From e6f8947c08115a73c29442c54455a094813c9cb0 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 7 Jul 2025 07:49:18 +0100 Subject: [PATCH] misc --- .vscode/launch.json | 26 +++++ .vscode/tasks.json | 6 +- .../Blogspot/FileSystemApiController.cs | 14 +-- src/Api/Controllers/Blogspot/MoveFileQuery.cs | 25 ++--- .../Controllers/Blogspot/RenameFileQuery.cs | 15 +++ .../Business/EstimateApiController.cs | 2 +- .../NativeConfidentialController.cs | 3 +- .../accounting/AccountController.cs | 7 +- src/Api/Helpers/RequestHelpers.cs | 4 +- src/Api/Helpers/UserHelpers.cs | 2 +- src/Api/Program.cs | 14 +-- src/Yavsc.Abstract/Constants.cs | 22 ++-- src/Yavsc.Server/Config.cs | 4 +- src/Yavsc.Server/Hubs/ChatHub.cs | 24 ++-- src/Yavsc.Server/Models/ApplicationUser.cs | 22 ++-- src/Yavsc.Server/Services/BlogPostEdition.cs | 13 +++ src/Yavsc.Server/Services/BlogSpotService.cs | 14 ++- src/Yavsc.Server/Services/MailSender.cs | 7 +- src/Yavsc.Server/Services/ProfileService.cs | 8 +- .../ViewModels/BlogSpot/BlogPostBase.cs | 1 - .../ViewModels/BlogSpot/BlogPostEdit.cs | 34 ++++++ src/Yavsc.Server/Yavsc.Server.csproj | 16 +-- .../Communicating/BlogspotController.cs | 32 +++--- src/Yavsc/Extensions/HostingExtensions.cs | 104 ++++++++++++++++-- src/Yavsc/Extensions/PermissionHandler.cs | 5 + src/Yavsc/Makefile | 10 +- src/Yavsc/Startup.cs | 2 - src/Yavsc/Views/Blogspot/Create.cshtml | 2 +- src/Yavsc/Yavsc.csproj | 13 ++- src/Yavsc/wwwroot/css/site.css | 6 + .../wwwroot/lib/bootstrap/scss/_nav.scss | 4 +- .../lib/bootstrap/scss/_variables.scss | 4 + .../bootstrap/scss/forms/_form-control.scss | 1 - .../lib/bootstrap/dist/css/bootstrap.css | 5 +- .../bootstrap/scss/forms/_form-control.scss | 1 - test/yavscTests/Startup.cs | 2 +- 36 files changed, 328 insertions(+), 146 deletions(-) create mode 100644 src/Api/Controllers/Blogspot/RenameFileQuery.cs create mode 100644 src/Yavsc.Server/Services/BlogPostEdition.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index 43c862cc..1bcbc59b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,32 @@ // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "C#: sampleWebAsWebApiClient Debug", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/sampleWebAsWebApiClient/sampleWebAsWebApiClient.csproj" + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug//", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, /* { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ac0bb937..919d8e68 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,8 +9,12 @@ "args": [ "build", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" + "/consoleloggerparameters:NoSummary;ForceNoAlign", + ], + "group": "build", + "isBuildCommand": true, + "isTestCommand": false, "problemMatcher": "$msCompile" }, { diff --git a/src/Api/Controllers/Blogspot/FileSystemApiController.cs b/src/Api/Controllers/Blogspot/FileSystemApiController.cs index efc9f062..c17ebd58 100644 --- a/src/Api/Controllers/Blogspot/FileSystemApiController.cs +++ b/src/Api/Controllers/Blogspot/FileSystemApiController.cs @@ -111,9 +111,9 @@ namespace Yavsc.ApiControllers var user = dbContext.Users.Single( u => u.Id == uid ); - var info = user.MoveUserFileToDir(query.id, query.to); + var info = user.MoveUserFileToDir(query.Id, query.To); if (!info.Done) return new BadRequestObjectResult(info); - return Ok(new { moved = query.id }); + return Ok(new { moved = query.Id }); } [HttpPost] @@ -124,21 +124,21 @@ namespace Yavsc.ApiControllers if (!ModelState.IsValid) { var idvr = new ValidRemoteUserFilePathAttribute(); - return this.BadRequest(new { id = idvr.IsValid(query.id), to = idvr.IsValid(query.to), errors = ModelState }); + return this.BadRequest(new { id = idvr.IsValid(query.Id), to = idvr.IsValid(query.To), errors = ModelState }); } - _logger.LogInformation($"Valid move query: {query.id} => {query.to}"); + _logger.LogInformation($"Valid move query: {query.Id} => {query.To}"); var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); var user = dbContext.Users.Single( u => u.Id == uid ); try { - if (Config.UserFilesOptions.FileProvider.GetFileInfo(Path.Combine(user.UserName, query.id)).Exists) + if (Config.UserFilesOptions.FileProvider.GetFileInfo(Path.Combine(user.UserName, query.Id)).Exists) { - var result = user.MoveUserFile(query.id, query.to); + var result = user.MoveUserFile(query.Id, query.To); if (!result.Done) return new BadRequestObjectResult(result); } else { - var result = user.MoveUserDir(query.id, query.to); + var result = user.MoveUserDir(query.Id, query.To); if (!result.Done) return new BadRequestObjectResult(result); } } diff --git a/src/Api/Controllers/Blogspot/MoveFileQuery.cs b/src/Api/Controllers/Blogspot/MoveFileQuery.cs index c043fa35..a62f79c9 100644 --- a/src/Api/Controllers/Blogspot/MoveFileQuery.cs +++ b/src/Api/Controllers/Blogspot/MoveFileQuery.cs @@ -1,23 +1,16 @@ using Yavsc.Attributes.Validation; namespace Yavsc.Models.FileSystem { - public class RenameFileQuery { - [ValidRemoteUserFilePath] - [YaStringLength(1, 512)] - public string id { get; set; } - [YaStringLength(0, 512)] - [ValidRemoteUserFilePath] - public string to { get; set; } - } - public class MoveFileQuery { - [ValidRemoteUserFilePath] - [YaStringLength(1, 512)] - public string id { get; set; } + public class MoveFileQuery + { + [ValidRemoteUserFilePath] + [YaStringLength(1, 512)] + public required string Id { get; set; } - [YaStringLength(0, 512)] - [ValidRemoteUserFilePath] - public string to { get; set; } - } + [YaStringLength(0, 512)] + [ValidRemoteUserFilePath] + public required string To { get; set; } + } } diff --git a/src/Api/Controllers/Blogspot/RenameFileQuery.cs b/src/Api/Controllers/Blogspot/RenameFileQuery.cs new file mode 100644 index 00000000..3cde9d98 --- /dev/null +++ b/src/Api/Controllers/Blogspot/RenameFileQuery.cs @@ -0,0 +1,15 @@ +using Yavsc.Attributes.Validation; +namespace Yavsc.Models.FileSystem +{ + public class RenameFileQuery + { + [ValidRemoteUserFilePath] + [YaStringLength(1, 512)] + public required string Id { get; set; } + + [YaStringLength(0, 512)] + [ValidRemoteUserFilePath] + public required string To { get; set; } + } + +} diff --git a/src/Api/Controllers/Business/EstimateApiController.cs b/src/Api/Controllers/Business/EstimateApiController.cs index a85ece2a..79b103e4 100644 --- a/src/Api/Controllers/Business/EstimateApiController.cs +++ b/src/Api/Controllers/Business/EstimateApiController.cs @@ -37,7 +37,7 @@ namespace Yavsc.Controllers } // GET: api/Estimate{?ownerId=User.GetUserId()} [HttpGet] - public IActionResult GetEstimates(string ownerId = null) + public IActionResult GetEstimates(string? ownerId = null) { if (ownerId == null) ownerId = User.GetUserId(); else if (!UserIsAdminOrThis(ownerId)) // throw new Exception("Not authorized") ; diff --git a/src/Api/Controllers/NativeConfidentialController.cs b/src/Api/Controllers/NativeConfidentialController.cs index 6adf68fd..8986c5fb 100644 --- a/src/Api/Controllers/NativeConfidentialController.cs +++ b/src/Api/Controllers/NativeConfidentialController.cs @@ -32,7 +32,8 @@ public class NativeConfidentialController : Controller [FromBody] DeviceDeclaration declaration) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - + if (uid == null) + throw new InvalidOperationException("no name identifier from claims"); if (!ModelState.IsValid) { _logger.LogError("Invalid model for GCMD"); diff --git a/src/Api/Controllers/accounting/AccountController.cs b/src/Api/Controllers/accounting/AccountController.cs index c1bb811a..01ff61be 100644 --- a/src/Api/Controllers/accounting/AccountController.cs +++ b/src/Api/Controllers/accounting/AccountController.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Yavsc.Models; using Yavsc.Api.Helpers; using Yavsc.Server.Helpers; +using System.Diagnostics; namespace Yavsc.WebApi.Controllers { @@ -30,9 +31,9 @@ namespace Yavsc.WebApi.Controllers return new BadRequestObjectResult( new { error = "user not found" }); var uid = User.GetUserId(); - + Debug.Assert(uid != null, "uid is null"); var userData = await GetUserData(uid); - + Debug.Assert(userData != null, "userData is null"); var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email, userData.Avatar, userData.PostalAddress, userData.DedicatedGoogleCalendar); @@ -57,7 +58,7 @@ namespace Yavsc.WebApi.Controllers [HttpGet("myhost")] public IActionResult MyHost () { - return Ok(new { host = Request.ForHost() }); + return Ok(new { host = Request.ForwardedFor() }); } diff --git a/src/Api/Helpers/RequestHelpers.cs b/src/Api/Helpers/RequestHelpers.cs index 2c2fae27..0ab687e0 100644 --- a/src/Api/Helpers/RequestHelpers.cs +++ b/src/Api/Helpers/RequestHelpers.cs @@ -13,8 +13,8 @@ namespace Yavsc.Api.Helpers public static class RequestHelpers { // Check for some apache proxy header, if any - public static string ForHost(this HttpRequest request) { - string host = request.Headers["X-Forwarded-For"]; + public static string? ForwardedFor(this HttpRequest request) { + string? host = request.Headers["X-Forwarded-For"]; if (string.IsNullOrEmpty(host)) { host = request.Host.Value; } else { // Using X-Forwarded-For last address diff --git a/src/Api/Helpers/UserHelpers.cs b/src/Api/Helpers/UserHelpers.cs index 2d480ea1..ee895600 100644 --- a/src/Api/Helpers/UserHelpers.cs +++ b/src/Api/Helpers/UserHelpers.cs @@ -9,7 +9,7 @@ namespace Yavsc.Api.Helpers { public static class UserHelpers { - public static string GetUserId(this ClaimsPrincipal user) + public static string? GetUserId(this ClaimsPrincipal user) { return user.FindFirstValue("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"); } diff --git a/src/Api/Program.cs b/src/Api/Program.cs index ef4a6667..543ae951 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -11,8 +11,6 @@ */ using IdentityModel; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Helpers; @@ -63,7 +61,8 @@ internal class Program options.IncludeErrorDetails = true; options.Authority = "https://localhost:5001"; options.TokenValidationParameters = - new() { ValidateAudience = false }; + new() { ValidateAudience = false, RoleClaimType = JwtClaimTypes.Role }; + options.MapInboundClaims = true; }); services.AddDbContext(options => @@ -74,9 +73,9 @@ internal class Program .AddTransient(); services.AddTransient(); /* + services.AddIdentityApiEndpoints(); services.AddSingleton(); services.AddSingleton(); - services.AddIdentityApiEndpoints(); services.AddSession(); */ WorkflowHelpers.ConfigureBillingService(); @@ -101,15 +100,10 @@ internal class Program 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.Abstract/Constants.cs b/src/Yavsc.Abstract/Constants.cs index b6c7bf2e..033cdecd 100644 --- a/src/Yavsc.Abstract/Constants.cs +++ b/src/Yavsc.Abstract/Constants.cs @@ -5,21 +5,21 @@ namespace Yavsc public static class Constants { - public static readonly Scope[] SiteScopes = {  - new Scope { Id = "profile", Description = "Your profile informations" },   - new Scope { Id = "book" , Description ="Your booking interface"},   - new Scope { Id = "blog" , Description ="Your blogging interface"},   - new Scope { Id = "estimate" , Description ="Your estimation interface"},   - new Scope { Id = "contract" , Description ="Your contract signature access"},  - new Scope { Id = "admin" , Description ="Your administration rights on this site"},  - new Scope { Id = "moderation" , Description ="Your moderator interface"},  + public static readonly Scope[] SiteScopes = { + new Scope { Id = "profile", Description = "Your profile informations" }, + new Scope { Id = "book" , Description ="Your booking interface"}, + new Scope { Id = "blog" , Description ="Your blogging interface"}, + new Scope { Id = "estimate" , Description ="Your estimation interface"}, + new Scope { Id = "contract" , Description ="Your contract signature access"}, + new Scope { Id = "admin" , Description ="Your administration rights on this site"}, + new Scope { Id = "moderation" , Description ="Your moderator interface"}, new Scope { Id = "frontoffice" , Description ="Your front office interface" } }; public const string CompanyClaimType = "https://schemas.pschneider.fr/identity/claims/Company"; public const string UserNameRegExp = @"^[a-zA-Z][a-zA-Z0-9._-]*$"; public const string UserFileNamePatternRegExp = @"^([a-zA-Z0-9._-]*/)*[a-zA-Z0-9._-]+$"; - + public const string LoginPath = "/signin"; public const string LogoutPath = "/signout"; @@ -52,9 +52,7 @@ namespace Yavsc public const int MaxUserNameLength = 26; public const string LivePath = "/live/cast"; - - public const string StreamingPath = "/api/stream/put"; - public const string RoleClaimName = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"; + public const string StreamingPath = "/api/stream/put"; } } diff --git a/src/Yavsc.Server/Config.cs b/src/Yavsc.Server/Config.cs index 26545aea..0c378a1d 100644 --- a/src/Yavsc.Server/Config.cs +++ b/src/Yavsc.Server/Config.cs @@ -80,12 +80,12 @@ public static class Config PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc", "http://localhost:5002/signout-callback-oidc" }, - AllowedScopes = { + AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, IdentityServerConstants.StandardScopes.OfflineAccess, - "scope2" } + "scope2" }, }, }; diff --git a/src/Yavsc.Server/Hubs/ChatHub.cs b/src/Yavsc.Server/Hubs/ChatHub.cs index 85a2330b..518575d9 100644 --- a/src/Yavsc.Server/Hubs/ChatHub.cs +++ b/src/Yavsc.Server/Hubs/ChatHub.cs @@ -29,11 +29,13 @@ using Microsoft.Extensions.Localization; namespace Yavsc { + using System.Diagnostics; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using Models; using Models.Chat; using Yavsc.Abstract.Chat; + using Yavsc.Helpers; using Yavsc.Services; public partial class ChatHub : Hub, IDisposable { @@ -192,10 +194,11 @@ namespace Yavsc NotifyUserInRoom(NotificationTypes.Error, room, "already registered."); return; } - string userName = Context.User.Identity.Name; + Debug.Assert(Context.User != null); + string userName = Context.User.GetUserName(); var user = _dbContext.Users.FirstOrDefault(u => u.UserName == userName); - var newroom = new ChatRoom { Name = room, OwnerId = user.Id }; + var newroom = new ChatRoom { Name = room, OwnerId = Context.User.GetUserId() }; ChatRoomInfo chanInfo; if (_cxManager.TryGetChanInfo(room, out chanInfo)) { @@ -319,7 +322,7 @@ namespace Yavsc async Task NotifyUser(string type, string targetId, string message) { - _logger.LogInformation("notifying user {type} {targetId} : {message}"); + _logger.LogInformation($"notifying user {type} {targetId} : {message}"); await Clients.Caller.SendAsync("notifyUser", type, targetId, message); } @@ -331,6 +334,8 @@ namespace Yavsc [Authorize] public async Task SendPV(string userName, string message) { + // Authorized code + Debug.Assert(Context.User != null); _logger.LogInformation($"Sending pv to {userName}"); if (!InputValidator.ValidateUserName(userName)) @@ -344,19 +349,21 @@ namespace Yavsc return ; } _logger.LogInformation($"Message form is validated."); + var identityUserName = Context.User.GetUserName(); - if (userName[0] != '?') + if (userName[0] != '?' && Context.User!=null) if (!Context.User.IsInRole(Constants.AdminGroupName)) { + var bl = _dbContext.BlackListed .Include(r => r.User) .Include(r => r.Owner) - .Where(r => r.User.UserName == Context.User.Identity.Name && r.Owner.UserName == userName) + .Where(r => r.User.UserName == identityUserName && r.Owner.UserName == userName) .Select(r => r.OwnerId); if (bl.Count() > 0) { - _logger.LogError($"Black listed : {Context.User.Identity.Name}"); + _logger.LogError($"Black listed : {identityUserName}"); await NotifyUser(NotificationTypes.PrivateMessageDenied, userName, "you are black listed."); return; } @@ -372,7 +379,7 @@ namespace Yavsc _logger.LogInformation($"cx: {connectionId}"); var cli = Clients.Client(connectionId); _logger.LogInformation($"cli: {cli.ToString()}"); - await cli.SendAsync("addPV", Context.User.Identity.Name, message); + await cli.SendAsync("addPV", identityUserName, message); _logger.LogInformation($"Sent pv to cx {connectionId}"); } } @@ -380,6 +387,9 @@ namespace Yavsc [Authorize] public async Task SendStream(string connectionId, long streamId, string message) { + // Authorized code + Debug.Assert(Context.User != null); + Debug.Assert(Context.User.Identity != null); if (!InputValidator.ValidateMessage(message)) return; var sender = Context.User.Identity.Name; var cli = Clients.Client(connectionId); diff --git a/src/Yavsc.Server/Models/ApplicationUser.cs b/src/Yavsc.Server/Models/ApplicationUser.cs index 314109b6..fa577c8b 100644 --- a/src/Yavsc.Server/Models/ApplicationUser.cs +++ b/src/Yavsc.Server/Models/ApplicationUser.cs @@ -42,24 +42,24 @@ namespace Yavsc.Models /// /// [InverseProperty("Author"), JsonIgnore] - public virtual List Posts { get; set; } + public virtual List? Posts { get; set; } /// /// User's contact list /// /// [InverseProperty("Owner"), JsonIgnore] - public virtual List Book { get; set; } + public virtual List? Book { get; set; } /// /// External devices using the API /// /// [InverseProperty("DeviceOwner"), JsonIgnore] - public virtual List DeviceDeclaration { get; set; } + public virtual List? DeviceDeclaration { get; set; } [InverseProperty("Owner"), JsonIgnore] - public virtual List Connections { get; set; } + public virtual List? Connections { get; set; } /// /// User's circles @@ -67,7 +67,7 @@ namespace Yavsc.Models /// [InverseProperty("Owner"), JsonIgnore] - public virtual List Circles { get; set; } + public virtual List? Circles { get; set; } /// /// Billing postal address @@ -82,14 +82,14 @@ namespace Yavsc.Models /// /// [MaxLength(512)] - public string DedicatedGoogleCalendar { get; set; } + public string? DedicatedGoogleCalendar { get; set; } public override string ToString() { return this.Id + " " + this.AccountBalance?.Credits.ToString() + this.Email + " " + this.UserName + " $" + this.AccountBalance?.Credits.ToString(); } - public virtual List BankInfo { get; set; } + public virtual List? BankInfo { get; set; } public long DiskQuota { get; set; } = 512 * 1024 * 1024; public long DiskUsage { get; set; } = 0; @@ -98,21 +98,21 @@ namespace Yavsc.Models [JsonIgnore] [InverseProperty("Owner")] - public virtual List BlackList { get; set; } + public virtual List? BlackList { get; set; } public bool AllowMonthlyEmail { get; set; } = false; [JsonIgnore] [InverseProperty("Owner")] - public virtual List Rooms { get; set; } + public virtual List? Rooms { get; set; } [JsonIgnore] [InverseProperty("User")] - public virtual List RoomAccess { get; set; } + public virtual List? RoomAccess { get; set; } [JsonIgnore] [InverseProperty("Member")] - public virtual List Membership { get; set; } + public virtual List? Membership { get; set; } IAccountBalance? IApplicationUser.AccountBalance => AccountBalance; diff --git a/src/Yavsc.Server/Services/BlogPostEdition.cs b/src/Yavsc.Server/Services/BlogPostEdition.cs new file mode 100644 index 00000000..da2fc9ce --- /dev/null +++ b/src/Yavsc.Server/Services/BlogPostEdition.cs @@ -0,0 +1,13 @@ +using Yavsc.Models.Blog; + +public class BlogPostEdition +{ + public string Content { get; internal set; } + public string Title { get; internal set; } + public string Photo { get; internal set; } + + internal static BlogPostEdition From(BlogPost blog) + { + throw new NotImplementedException(); + } +} diff --git a/src/Yavsc.Server/Services/BlogSpotService.cs b/src/Yavsc.Server/Services/BlogSpotService.cs index 91f7bbd7..99146a1a 100644 --- a/src/Yavsc.Server/Services/BlogSpotService.cs +++ b/src/Yavsc.Server/Services/BlogSpotService.cs @@ -25,7 +25,7 @@ public class BlogSpotService _context = context; } - public BlogPost Create(string userId, BlogPostBase blogInput) + public BlogPost Create(string userId, BlogPostEditViewModel blogInput) { BlogPost post = new BlogPost { @@ -38,7 +38,7 @@ public class BlogSpotService _context.SaveChanges(userId); return post; } - public async Task GetPostForEdition(ClaimsPrincipal user, long blogPostId) + public async Task GetPostForEdition(ClaimsPrincipal user, long blogPostId) { var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId); var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission()); @@ -46,7 +46,7 @@ public class BlogSpotService { throw new AuthorizationFailureException(auth); } - return blog; + return BlogPostEditViewModel.From(blog); } public async Task Details(ClaimsPrincipal user, long blogPostId) @@ -182,4 +182,12 @@ public class BlogSpotService x => x.DateCreated ).ToList(); } + + public async Task GetBlogPostAsync(long value) + { + return await _context.BlogSpot + .Include(b => b.Author) + .Include(b => b.ACL) + .SingleOrDefaultAsync(x => x.Id == value); + } } diff --git a/src/Yavsc.Server/Services/MailSender.cs b/src/Yavsc.Server/Services/MailSender.cs index 8f2c2052..a9ccac35 100644 --- a/src/Yavsc.Server/Services/MailSender.cs +++ b/src/Yavsc.Server/Services/MailSender.cs @@ -1,13 +1,9 @@ -using System.Text; -using System; using System.Net; -using System.Threading.Tasks; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MimeKit; -using Yavsc.Abstract.Manage; using Microsoft.AspNetCore.Identity; using Yavsc.Interface; using Yavsc.Settings; @@ -27,8 +23,7 @@ namespace Yavsc.Services public MailSender( IOptions sitesOptions, IOptions smtpOptions, - ILoggerFactory loggerFactory, - IStringLocalizer localizer + ILoggerFactory loggerFactory ) { this.localizer = localizer; diff --git a/src/Yavsc.Server/Services/ProfileService.cs b/src/Yavsc.Server/Services/ProfileService.cs index 3239672a..c2f7ded7 100644 --- a/src/Yavsc.Server/Services/ProfileService.cs +++ b/src/Yavsc.Server/Services/ProfileService.cs @@ -57,18 +57,18 @@ namespace Yavsc.Services var roles = await this._userManager.GetRolesAsync(user); if (roles.Count()>0) { - claims.AddRange(roles.Select(r => new Claim(Constants.RoleClaimName, r))); + claims.AddRange(roles.Select(r => new Claim(JwtClaimTypes.Role, r))); } } return claims; } - public async Task GetProfileDataAsync(ProfileDataRequestContext context) + public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var subjectId = GetSubjectId(context.Subject); - if (subjectId==null) return; + if (subjectId == null) return; var user = await _userManager.FindByIdAsync(subjectId); - if (user==null) return ; + if (user == null) return; context.IssuedClaims = await GetClaimsFromUserAsync(context, user); } diff --git a/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostBase.cs b/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostBase.cs index 24644a5e..cd431608 100644 --- a/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostBase.cs +++ b/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostBase.cs @@ -21,7 +21,6 @@ namespace Yavsc.ViewModels.Blog [Display(Name = "Liste de contrôle d'accès")] public virtual List? ACL { get; set; } - public bool Publish { get; set; } } } diff --git a/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostEdit.cs b/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostEdit.cs index 1c84934c..f384664c 100644 --- a/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostEdit.cs +++ b/src/Yavsc.Server/ViewModels/BlogSpot/BlogPostEdit.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Yavsc.Models.Blog; namespace Yavsc.ViewModels.Blog; @@ -8,4 +9,37 @@ public class BlogPostEditViewModel : BlogPostBase [Required] public required long Id { get; set; } + + public bool Publish { get; set; } + + public BlogPostEditViewModel() + { + + } + + + public static BlogPostEditViewModel From(BlogPost blogInput) + { + return new BlogPostEditViewModel + { + Id = blogInput.Id, + Title = blogInput.Title, + Publish = false, + Photo = blogInput.Photo, + Content = blogInput.Content, + ACL = blogInput.ACL + }; + } + public static BlogPostEditViewModel FromViewModel(BlogPostEditViewModel blogInput) + { + return new BlogPostEditViewModel + { + Id = blogInput.Id, + Title = blogInput.Title, + Publish = false, + Photo = blogInput.Photo, + Content = blogInput.Content, + ACL = blogInput.ACL + }; + } } diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 0ae56115..31044854 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -8,7 +8,7 @@ - + @@ -19,20 +19,14 @@ - + - + - - + + - - - - - - diff --git a/src/Yavsc/Controllers/Communicating/BlogspotController.cs b/src/Yavsc/Controllers/Communicating/BlogspotController.cs index 6f0ff24a..2098db64 100644 --- a/src/Yavsc/Controllers/Communicating/BlogspotController.cs +++ b/src/Yavsc/Controllers/Communicating/BlogspotController.cs @@ -69,12 +69,19 @@ namespace Yavsc.Controllers public async Task Details(long? id) { if (id == null) return this.NotFound(); + try + { + var blog = await blogSpotService.Details(User, id.Value); + ViewData["apicmtctlr"] = "/api/blogcomments"; + ViewData["moderatoFlag"] = User.IsInRole(Constants.BlogModeratorGroupName); - var blog = await blogSpotService.Details(User, id.Value); - ViewData["apicmtctlr"] = "/api/blogcomments"; - ViewData["moderatoFlag"] = User.IsInRole(Constants.BlogModeratorGroupName); + return View(blog); - return View(blog); + } + catch (AuthorizationFailureException ex) + { + return Challenge(); + } } void SetLangItems() { @@ -98,12 +105,12 @@ namespace Yavsc.Controllers // POST: Blog/Create [HttpPost, Authorize, ValidateAntiForgeryToken] - public IActionResult Create(BlogPostBase blogInput) + public IActionResult Create(BlogPostEditViewModel blogInput) { if (ModelState.IsValid) { BlogPost post = blogSpotService.Create(User.GetUserId(), - blogInput); + BlogPostEditViewModel.FromViewModel(blogInput)); return RedirectToAction("Index"); } return View("Edit", blogInput); @@ -119,20 +126,13 @@ namespace Yavsc.Controllers } try { - BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value); + var blog = await blogSpotService.GetPostForEdition(User, id.Value); if (blog == null) { return NotFound(); } SetLangItems(); - return View(new BlogPostEditViewModel - { - Id = blog.Id, - Title = blog.Title, - Content = blog.Content, - ACL = blog.ACL, - Photo = blog.Photo - }); + return View(blog); } catch (AuthorizationFailureException) @@ -164,7 +164,7 @@ namespace Yavsc.Controllers return NotFound(); } - BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value); + var blog = await blogSpotService.GetBlogPostAsync(id.Value); if (blog == null) { return NotFound(); diff --git a/src/Yavsc/Extensions/HostingExtensions.cs b/src/Yavsc/Extensions/HostingExtensions.cs index af8a533f..df035487 100644 --- a/src/Yavsc/Extensions/HostingExtensions.cs +++ b/src/Yavsc/Extensions/HostingExtensions.cs @@ -33,12 +33,16 @@ using Yavsc.Server.Helpers; using System.Security.Cryptography; using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Protocols.Configuration; +using IdentityModel; +using System.Security.Claims; +using IdentityServer8.Security; namespace Yavsc.Extensions; public static class HostingExtensions { + public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app, bool enableDirectoryBrowsing = false) { @@ -191,11 +195,13 @@ public static class HostingExtensions { policy .RequireAuthenticatedUser() - .RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", "Performer"); + .RequireClaim(JwtClaimTypes.Role, "Performer"); }); options.AddPolicy("AdministratorOnly", policy => { - _ = policy.RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Constants.AdminGroupName); + _ = policy + .RequireAuthenticatedUser() + .RequireClaim(JwtClaimTypes.Role, Constants.AdminGroupName); }); options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)); @@ -284,12 +290,13 @@ public static class HostingExtensions // see https://IdentityServer8.readthedocs.io/en/latest/topics/resources.html options.EmitStaticAudienceClaim = true; + }) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryClients(Config.Clients) .AddInMemoryApiScopes(Config.ApiScopes) - .AddAspNetIdentity() .AddProfileService() + .AddAspNetIdentity() ; if (builder.Environment.IsDevelopment()) { @@ -306,9 +313,9 @@ public static class HostingExtensions RSA rsa = RSA.Create(); rsa.ImportFromPem(File.ReadAllText(certFileInfo.FullName)); var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256) - { - CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } - }; + { + CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } + }; identityServerBuilder.AddSigningCredential(signingCredentials); } return identityServerBuilder; @@ -372,7 +379,7 @@ public static class HostingExtensions app.UseAuthorization(); app.UseCors("default"); app.MapDefaultControllerRoute(); - //pp.MapRazorPages(); + //app.MapRazorPages(); app.MapHub("/chatHub"); WorkflowHelpers.ConfigureBillingService(); @@ -408,3 +415,86 @@ public static class HostingExtensions } } } + +public class MyIdentityStore : IUserClaimStore +{ + public Task AddClaimsAsync(IdentityUser user, IEnumerable claims, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task CreateAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task DeleteAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Task FindByIdAsync(string userId, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task> GetClaimsAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task GetNormalizedUserNameAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task GetUserIdAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task GetUserNameAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task RemoveClaimsAsync(IdentityUser user, IEnumerable claims, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task ReplaceClaimAsync(IdentityUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task SetNormalizedUserNameAsync(IdentityUser user, string? normalizedName, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task SetUserNameAsync(IdentityUser user, string? userName, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task UpdateAsync(IdentityUser user, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} diff --git a/src/Yavsc/Extensions/PermissionHandler.cs b/src/Yavsc/Extensions/PermissionHandler.cs index 34082fea..4bee4f4c 100644 --- a/src/Yavsc/Extensions/PermissionHandler.cs +++ b/src/Yavsc/Extensions/PermissionHandler.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using RazorEngine.Compilation.ImpromptuInterface.Optimization; using Yavsc.Helpers; +using Yavsc.Migrations; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.ViewModels.Auth; @@ -33,6 +34,10 @@ public class PermissionHandler : IAuthorizationHandler { context.Succeed(requirement); } + else if (context.User.IsInRole("Administrator")) + { + context.Succeed(requirement); + } } else if (requirement is EditPermission || requirement is DeletePermission) { diff --git a/src/Yavsc/Makefile b/src/Yavsc/Makefile index 1d0cf188..7fd5529b 100644 --- a/src/Yavsc/Makefile +++ b/src/Yavsc/Makefile @@ -20,11 +20,11 @@ install_service: sudo systemctl enable yavsc pushInProd: publish - sudo systemctl stop $(SERVICE_PROD) - sudo cp -a bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish/* $(DESTDIR) - sudo chown -R $(USER_AND_GROUP) $(DESTDIR) - sudo sync - sudo systemctl start $(SERVICE_PROD) + @sudo systemctl stop $(SERVICE_PROD) + @sudo cp -a bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish/* $(DESTDIR) + @sudo chown -R $(USER_AND_GROUP) $(DESTDIR) + @sudo sync + @sudo systemctl start $(SERVICE_PROD) %.min.js: %.js jsmin < $^ > $@ diff --git a/src/Yavsc/Startup.cs b/src/Yavsc/Startup.cs index d3703ea3..d24a68b5 100644 --- a/src/Yavsc/Startup.cs +++ b/src/Yavsc/Startup.cs @@ -1,7 +1,5 @@ -using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; -using Yavsc.Helpers; using Yavsc.Server.Helpers; using Yavsc.Settings; diff --git a/src/Yavsc/Views/Blogspot/Create.cshtml b/src/Yavsc/Views/Blogspot/Create.cshtml index 2625cdc9..cee73565 100644 --- a/src/Yavsc/Views/Blogspot/Create.cshtml +++ b/src/Yavsc/Views/Blogspot/Create.cshtml @@ -1,4 +1,4 @@ -@model Yavsc.ViewModels.Blog.BlogPostBase +@model BlogPostEditViewModel @{ ViewData["Title"] = "Blog post edition"; diff --git a/src/Yavsc/Yavsc.csproj b/src/Yavsc/Yavsc.csproj index 7454edb4..9a562b11 100644 --- a/src/Yavsc/Yavsc.csproj +++ b/src/Yavsc/Yavsc.csproj @@ -10,14 +10,15 @@ all - + all - + + - + @@ -30,15 +31,15 @@ - + - + - + diff --git a/src/Yavsc/wwwroot/css/site.css b/src/Yavsc/wwwroot/css/site.css index e62c7f4e..70e306e8 100644 --- a/src/Yavsc/wwwroot/css/site.css +++ b/src/Yavsc/wwwroot/css/site.css @@ -35,3 +35,9 @@ img.blogphoto { padding: .3em; border: solid black 1px; } + +input[type='checkbox'] { + appearance: auto; + min-width: 1em; + min-height: 1em; +} diff --git a/src/Yavsc/wwwroot/lib/bootstrap/scss/_nav.scss b/src/Yavsc/wwwroot/lib/bootstrap/scss/_nav.scss index 3b33798a..ff073d36 100644 --- a/src/Yavsc/wwwroot/lib/bootstrap/scss/_nav.scss +++ b/src/Yavsc/wwwroot/lib/bootstrap/scss/_nav.scss @@ -10,7 +10,6 @@ @include rfs($nav-link-font-size, --#{$prefix}nav-link-font-size); --#{$prefix}nav-link-font-weight: #{$nav-link-font-weight}; --#{$prefix}nav-link-color: #{$nav-link-color}; - --#{$prefix}nav-link-background-color: #{$dropdown-dark-bg}; --#{$prefix}nav-link-hover-color: #{$nav-link-hover-color}; --#{$prefix}nav-link-disabled-color: #{$nav-link-disabled-color}; // scss-docs-end nav-css-vars @@ -28,9 +27,8 @@ @include font-size(var(--#{$prefix}nav-link-font-size)); font-weight: var(--#{$prefix}nav-link-font-weight); color: var(--#{$prefix}nav-link-color); - background-color: var(--#{$prefix}nav-link-background-color); text-decoration: if($link-decoration == none, null, none); - background-color: var(--#{$prefix}nav-tabs-link-active-bg); + background: none; border: 0; @include transition($nav-link-transition); diff --git a/src/Yavsc/wwwroot/lib/bootstrap/scss/_variables.scss b/src/Yavsc/wwwroot/lib/bootstrap/scss/_variables.scss index 5246fb87..06531395 100644 --- a/src/Yavsc/wwwroot/lib/bootstrap/scss/_variables.scss +++ b/src/Yavsc/wwwroot/lib/bootstrap/scss/_variables.scss @@ -1607,6 +1607,7 @@ $list-group-action-active-color: var(--#{$prefix}body-color) !default; $list-group-action-active-bg: var(--#{$prefix}secondary-bg) !default; // scss-docs-end list-group-variables + // Image thumbnails // scss-docs-start thumbnail-variables @@ -1618,6 +1619,7 @@ $thumbnail-border-radius: var(--#{$prefix}border-radius) !default; $thumbnail-box-shadow: var(--#{$prefix}box-shadow-sm) !default; // scss-docs-end thumbnail-variables + // Figures // scss-docs-start figure-variables @@ -1745,3 +1747,5 @@ $kbd-bg: var(--#{$prefix}body-color) !default; $nested-kbd-font-weight: null !default; // Deprecated in v5.2.0, removing in v6 $pre-color: null !default; + +@import "variables-dark"; // TODO: can be removed safely in v6, only here to avoid breaking changes in v5.3 diff --git a/src/Yavsc/wwwroot/lib/bootstrap/scss/forms/_form-control.scss b/src/Yavsc/wwwroot/lib/bootstrap/scss/forms/_form-control.scss index 67ae5f4f..e31e0dba 100644 --- a/src/Yavsc/wwwroot/lib/bootstrap/scss/forms/_form-control.scss +++ b/src/Yavsc/wwwroot/lib/bootstrap/scss/forms/_form-control.scss @@ -11,7 +11,6 @@ font-weight: $input-font-weight; line-height: $input-line-height; color: $input-color; - appearance: none; // Fix appearance for date inputs in Safari background-color: $input-bg; background-clip: padding-box; border: $input-border-width solid $input-border-color; diff --git a/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/dist/css/bootstrap.css index cb819ec2..0fa80aab 100644 --- a/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/dist/css/bootstrap.css +++ b/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/dist/css/bootstrap.css @@ -2120,9 +2120,6 @@ progress { font-weight: 400; line-height: 1.5; color: var(--bs-body-color); - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; background-color: var(--bs-body-bg); background-clip: padding-box; border: var(--bs-border-width) solid var(--bs-border-color); @@ -12040,4 +12037,4 @@ textarea.form-control-lg { } } -/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/scss/forms/_form-control.scss b/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/scss/forms/_form-control.scss index 67ae5f4f..e31e0dba 100644 --- a/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/scss/forms/_form-control.scss +++ b/src/sampleWebAsWebApiClient/wwwroot/lib/bootstrap/scss/forms/_form-control.scss @@ -11,7 +11,6 @@ font-weight: $input-font-weight; line-height: $input-line-height; color: $input-color; - appearance: none; // Fix appearance for date inputs in Safari background-color: $input-bg; background-clip: padding-box; border: $input-border-width solid $input-border-color; diff --git a/test/yavscTests/Startup.cs b/test/yavscTests/Startup.cs index 63f4e04a..1576785b 100644 --- a/test/yavscTests/Startup.cs +++ b/test/yavscTests/Startup.cs @@ -127,7 +127,7 @@ namespace yavscTests services.AddAuthorizationBuilder() .AddPolicy("AdministratorOnly", policy => { - policy.RequireClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Constants.AdminGroupName); + policy.RequireClaim(JwtClaimType.Role, Constants.AdminGroupName); }) .AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)) .AddPolicy("Bearer", new AuthorizationPolicyBuilder()