3 Commits

Author SHA1 Message Date
1e568279ad Authorizations
Some checks failed
Dotnet build and test / log-the-inputs (push) Failing after 2s
Dotnet build and test / build (push) Failing after 2s
2025-08-18 11:27:13 +01:00
541b891c3b restores the activitiy setup pages 2025-08-18 11:19:30 +01:00
2615f65515 Gice a chance to import/export at failing migration 2025-08-18 09:30:22 +01:00
18 changed files with 381 additions and 32 deletions

View File

@ -18,7 +18,7 @@ namespace Yavsc.Controllers
using Yavsc.Server.Helpers; using Yavsc.Server.Helpers;
[Produces("application/json")] [Produces("application/json")]
[Route("api/bookquery"), Authorize(Roles = "Performer,Administrator")] [Route("api/bookquery"), Authorize("Performer")]
public class BookQueryApiController : Controller public class BookQueryApiController : Controller
{ {
private ApplicationDbContext _context; private ApplicationDbContext _context;

View File

@ -28,7 +28,7 @@ namespace Yavsc.Controllers
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
/// <returns></returns> /// <returns></returns>
[Authorize(Roles="Performer"),HttpGet("{id}")] [Authorize("Performer"),HttpGet("{id}")]
public IActionResult Get(string id) public IActionResult Get(string id)
{ {
var pfr = dbContext.Performers.Include( var pfr = dbContext.Performers.Include(

View File

@ -12,7 +12,7 @@ using Yavsc.Server.Helpers;
namespace Yavsc.Controllers namespace Yavsc.Controllers
{ {
[Produces("application/json"),Authorize(Roles="Administrator")] [Produces("application/json"),Authorize("AdministratorOnly")]
[Route("api/users")] [Route("api/users")]
public class ApplicationUserApiController : Controller public class ApplicationUserApiController : Controller
{ {

View File

@ -13,6 +13,7 @@
using IdentityModel; using IdentityModel;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Yavsc;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Interface; using Yavsc.Interface;
using Yavsc.Models; using Yavsc.Models;
@ -61,7 +62,7 @@ internal class Program
options.IncludeErrorDetails = true; options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001"; options.Authority = "https://localhost:5001";
options.TokenValidationParameters = options.TokenValidationParameters =
new() { ValidateAudience = false, RoleClaimType = JwtClaimTypes.Role }; new() { ValidateAudience = false, RoleClaimType = Constants.RoleClaimType };
options.MapInboundClaims = true; options.MapInboundClaims = true;
}); });

View File

@ -54,5 +54,7 @@ namespace Yavsc
public const string LivePath = "/live/cast"; public const string LivePath = "/live/cast";
public const string StreamingPath = "/api/stream/put"; public const string StreamingPath = "/api/stream/put";
public static string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
} }
} }

View File

@ -43,7 +43,7 @@ namespace Yavsc.Services
claimAdds.Remove("profile"); claimAdds.Remove("profile");
claimAdds.Add(JwtClaimTypes.Name); claimAdds.Add(JwtClaimTypes.Name);
claimAdds.Add(JwtClaimTypes.Email); claimAdds.Add(JwtClaimTypes.Email);
claimAdds.Add(JwtClaimTypes.Role); claimAdds.Add(Constants.RoleClaimType);
} }
if (claimAdds.Contains(JwtClaimTypes.Name)) if (claimAdds.Contains(JwtClaimTypes.Name))
@ -52,12 +52,12 @@ namespace Yavsc.Services
if (claimAdds.Contains(JwtClaimTypes.Email)) if (claimAdds.Contains(JwtClaimTypes.Email))
claims.Add(new Claim(JwtClaimTypes.Email, user.Email)); claims.Add(new Claim(JwtClaimTypes.Email, user.Email));
if (claimAdds.Contains(JwtClaimTypes.Role)) if (claimAdds.Contains(Constants.RoleClaimType))
{ {
var roles = await this._userManager.GetRolesAsync(user); var roles = await this._userManager.GetRolesAsync(user);
if (roles.Count()>0) if (roles.Count()>0)
{ {
claims.AddRange(roles.Select(r => new Claim(JwtClaimTypes.Role, r))); claims.AddRange(roles.Select(r => new Claim(Constants.RoleClaimType, r)));
} }
} }
return claims; return claims;

View File

@ -403,7 +403,7 @@ namespace Yavsc.Controllers
} }
[Authorize(Roles = Constants.AdminGroupName)] [Authorize("AdministratorOnly")]
public IActionResult Index() public IActionResult Index()
{ {
IViewComponentHelper h; IViewComponentHelper h;
@ -411,7 +411,7 @@ namespace Yavsc.Controllers
return View(); return View();
} }
[Authorize(Roles = Constants.AdminGroupName)] [Authorize("AdministratorOnly")]
[Route("Account/UserList/{pageNum}/{len?}")] [Route("Account/UserList/{pageNum}/{len?}")]
public async Task<IActionResult> UserList(int pageNum, int pageLen = defaultLen) public async Task<IActionResult> UserList(int pageNum, int pageLen = defaultLen)
{ {

View File

@ -93,7 +93,7 @@ namespace Yavsc.Controllers
return Ok(new { message = "you owned it." }); return Ok(new { message = "you owned it." });
} }
[Authorize(Roles = Constants.AdminGroupName)] [Authorize("AdministratorOnly")]
[Produces("application/json")] [Produces("application/json")]
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {

View File

@ -25,13 +25,13 @@ namespace Yavsc.Controllers
public IActionResult GetBlog() public IActionResult GetBlog()
{ {
var data = applicationDbContext.BlogSpot.ToArray(); var data = applicationDbContext.BlogSpot.ToArray();
return Ok(JsonConvert.SerializeObject(data, Formatting.None)); return Ok(data);
} }
public IActionResult GetUsers() public IActionResult GetUsers()
{ {
var data = applicationDbContext.Users.ToArray(); var data = applicationDbContext.Users.ToArray();
return Ok(JsonConvert.SerializeObject(data, Formatting.None)); return Ok(data);
} }
public IActionResult ImportUsers(String usersJson) public IActionResult ImportUsers(String usersJson)

View File

@ -50,9 +50,8 @@ namespace Yavsc.Controllers
private List<SelectListItem> GetEligibleParent(string code) private List<SelectListItem> GetEligibleParent(string code)
{ {
// eligibles are those // eligibles are those
// who are not in descendants // who are not in descendence
//
var acts = _context.Activities.Where( var acts = _context.Activities.Where(
a => a.Code != code a => a.Code != code
).Select(a => new SelectListItem ).Select(a => new SelectListItem
@ -68,13 +67,13 @@ namespace Yavsc.Controllers
var pi = acts.FirstOrDefault(i => i.Value == existing.ParentCode); var pi = acts.FirstOrDefault(i => i.Value == existing.ParentCode);
if (pi!=null) pi.Selected = true; if (pi!=null) pi.Selected = true;
else nullItem.Selected = true; else nullItem.Selected = true;
RecFilterChild(acts, existing); RecursivelyFilterChild(acts, existing);
return acts; return acts;
} }
/// <summary> /// <summary>
/// Filters a activity selection list /// Filters a activity selection list
/// in order to exculde any descendant /// in order to exclude any descendant
/// from the eligible list at the <c>Parent</c> property. /// from the eligible list at the <c>Parent</c> property.
/// WARN! results in a infinite loop when /// WARN! results in a infinite loop when
/// data is corrupted and there is a circularity /// data is corrupted and there is a circularity
@ -82,22 +81,19 @@ namespace Yavsc.Controllers
/// </summary> /// </summary>
/// <param name="list"></param> /// <param name="list"></param>
/// <param name="activity"></param> /// <param name="activity"></param>
private static void RecFilterChild(List<SelectListItem> list, Activity activity) private static void RecursivelyFilterChild(List<SelectListItem> list, Activity activity)
{ {
if (activity == null) return; if (activity == null) return;
if (activity.Children == null) return; if (activity.Children == null) return;
if (list.Count == 0) return; if (list.Count == 0) return;
foreach (var child in activity.Children) foreach (var child in activity.Children)
{ {
RecFilterChild(list, child); RecursivelyFilterChild(list, child);
var rem = list.FirstOrDefault(i => i.Value == child.Code); var rem = list.FirstOrDefault(i => i.Value == child.Code);
if (rem != null) list.Remove(rem); if (rem != null) list.Remove(rem);
} }
} }
// GET: Activity/Details/5 // GET: Activity/Details/5
public IActionResult Details(string id) public IActionResult Details(string id)
{ {

View File

@ -7,7 +7,7 @@ using Yavsc.Server.Helpers;
namespace Yavsc.Controllers namespace Yavsc.Controllers
{ {
[Authorize(Roles="Administrator")] [Authorize("AdministratorOnly")]
public class SIRENExceptionsController : Controller public class SIRENExceptionsController : Controller
{ {
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;

View File

@ -5,7 +5,7 @@ using Yavsc.Controllers.Generic;
namespace Yavsc.Controllers namespace Yavsc.Controllers
{ {
[Authorize(Roles="Performer")] [Authorize("Performer")]
public class BrusherProfileController : SettingsController<BrusherProfile> public class BrusherProfileController : SettingsController<BrusherProfile>
{ {
public BrusherProfileController(ApplicationDbContext context) : base(context) public BrusherProfileController(ApplicationDbContext context) : base(context)

View File

@ -128,11 +128,10 @@ public static class HostingExtensions
{ {
options.SignIn.RequireConfirmedAccount = true; options.SignIn.RequireConfirmedAccount = true;
options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.PreferredUserName; options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.PreferredUserName;
options.ClaimsIdentity.RoleClaimType = JwtClaimTypes.Role; options.ClaimsIdentity.RoleClaimType = Constants.RoleClaimType;
} }
) )
.AddEntityFrameworkStores<ApplicationDbContext>(); .AddEntityFrameworkStores<ApplicationDbContext>();
} }
private static void AddYavscPolicies(IServiceCollection services) private static void AddYavscPolicies(IServiceCollection services)
@ -144,17 +143,20 @@ public static class HostingExtensions
policy.RequireAuthenticatedUser() policy.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2"); .RequireClaim("scope", "scope2");
}); });
options.AddPolicy("Performer", policy => options.AddPolicy("Performer", policy =>
{ {
policy policy
.RequireAuthenticatedUser() .RequireAuthenticatedUser()
.RequireClaim(JwtClaimTypes.Role, "Performer"); .RequireClaim(Constants.RoleClaimType,
new string[] {Constants.PerformerGroupName, Constants.AdminGroupName})
;
}); });
options.AddPolicy("AdministratorOnly", policy => options.AddPolicy("AdministratorOnly", policy =>
{ {
_ = policy _ = policy
.RequireAuthenticatedUser() .RequireAuthenticatedUser()
.RequireClaim(JwtClaimTypes.Role, Constants.AdminGroupName); .RequireClaim(Constants.RoleClaimType, Constants.AdminGroupName);
}); });
options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)); options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName));
@ -241,7 +243,12 @@ public static class HostingExtensions
.AddInMemoryApiScopes(Config.TestingApiScopes) .AddInMemoryApiScopes(Config.TestingApiScopes)
.AddAspNetIdentity<ApplicationUser>(); .AddAspNetIdentity<ApplicationUser>();
builder.Services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = JwtClaimTypes.Subject;
options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name;
options.ClaimsIdentity.RoleClaimType = Constants.RoleClaimType;
});
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())
{ {
@ -303,6 +310,8 @@ public static class HostingExtensions
public async static Task<WebApplication> ConfigurePipeline(this WebApplication app) public async static Task<WebApplication> ConfigurePipeline(this WebApplication app)
{ {
ILoggerFactory loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<Program>();
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {
@ -311,12 +320,19 @@ public static class HostingExtensions
else else
{ {
app.UseExceptionHandler("/Home/Error"); app.UseExceptionHandler("/Home/Error");
try
{
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await db.Database.MigrateAsync(); await db.Database.MigrateAsync();
} }
} }
catch (Exception ex)
{
logger.LogError("Error migrating the database : {0}", ex);
}
}
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
@ -345,7 +361,6 @@ public static class HostingExtensions
WorkflowHelpers.ConfigureBillingService(); WorkflowHelpers.ConfigureBillingService();
var services = app.Services; var services = app.Services;
ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>();
var siteSettings = services.GetRequiredService<IOptions<SiteSettings>>(); var siteSettings = services.GetRequiredService<IOptions<SiteSettings>>();
var smtpSettings = services.GetRequiredService<IOptions<SmtpSettings>>(); var smtpSettings = services.GetRequiredService<IOptions<SmtpSettings>>();
var payPalSettings = services.GetRequiredService<IOptions<PayPalSettings>>(); var payPalSettings = services.GetRequiredService<IOptions<PayPalSettings>>();

View File

@ -0,0 +1,83 @@
@model Activity
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Activity</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Code" class="col-md-2 control-label">Code</label>
<div class="col-md-10">
<input asp-for="Code" class="form-control" />
<span asp-validation-for="Code" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Name" class="col-md-2 control-label">
Name"]</label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Parent" class="col-md-2 control-label">
Parent"]</label>
<div class="col-md-10">
<select asp-for="ParentCode" asp-items=@ViewBag.ParentCode class="form-control" >
</select>
<span asp-validation-for="ParentCode" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Description" class="col-md-2 control-label">
Description"]</label>
<div class="col-md-10">
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Photo" class="col-md-2 control-label">
Photo"]
</label>
<div class="col-md-10">
<input asp-for="Photo" class="form-control" />
<span asp-validation-for="Photo" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="SettingsClassName" class="col-md-2 control-label">
SettingsClass"]
</label>
<div class="col-md-10">
<select asp-for="SettingsClassName" class="form-control" asp-items="@ViewBag.SettingsClassName">
</select>
<span asp-validation-for="SettingsClassName" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create"]" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@ -0,0 +1,40 @@
@model Activity
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<h3>AreYourSureYouWantToDeleteThis</h3>
<div>
<h4>Activity</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Description)
</dt>
<dd>
@Html.DisplayFor(model => model.Description)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Photo)
</dt>
<dd>
@Html.DisplayFor(model => model.Photo)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-danger" />
<a asp-action="Index" class="btn btn-link">Back to List</a>
</div>
</form>
</div>

View File

@ -0,0 +1,37 @@
@model Activity
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Activity</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Description)
</dt>
<dd>
@Html.DisplayFor(model => model.Description)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Photo)
</dt>
<dd>
@Html.DisplayFor(model => model.Photo)
<img src="@Model.Photo" style="max-width:100%"/>
</dd>
</dl>
</div>
<p>
<a asp-action="Edit" asp-route-id="@Model.Code">Edit</a> |
<a asp-action="Index">Back to List</a>
</p>

View File

@ -0,0 +1,86 @@
@model Activity
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Activity @Model.Code</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Code" />
<div class="form-group">
<label asp-for="Name" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Parent" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="ParentCode" asp-items=@ViewBag.ParentCode class="form-control" >
</select>
<span asp-validation-for="ParentCode" class="text-danger" >
</span>
</div>
</div>
<div class="form-group">
<label asp-for="Description" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger" >
</span>
</div>
</div>
<div class="form-group">
<label asp-for="Photo" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Photo" class="form-control" />
<span asp-validation-for="Photo" class="text-danger">
</span>
</div>
</div>
<div class="form-group">
<label asp-for="SettingsClassName" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="SettingsClassName" class="form-control" asp-items="@ViewBag.SettingsClassName">
</select>
<span asp-validation-for="SettingsClassName" class="text-danger" ></span>
</div>
</div>
<div class="form-group">
<label asp-for="Hidden" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Hidden" class="form-control" />
<span asp-validation-for="Hidden" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Rate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Rate" class="form-control" />
<span asp-validation-for="Rate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@ -0,0 +1,89 @@
@model IEnumerable<Activity>
@{
ViewData["Title"] = "Index";
}
@section scripts {
<script>
$(document).ready(function(){
$("tr[data-hidden=True]").css('background-color','grey')
})
</script>
}
<h2>@ViewData["Title"]</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Code)
</th>
<th>
@Html.DisplayNameFor(model => model.Description)
</th>
<th>
@Html.DisplayNameFor(model => model.Photo)
</th>
<th>
@Html.DisplayNameFor(model => model.Parent)
</th>
<th>
@Html.DisplayNameFor(model => model.SettingsClassName)
</th>
<th>
@Html.DisplayNameFor(model => model.Children)
</th>
<th>
@Html.DisplayNameFor(model => model.Rate)
</th>
</tr>
@foreach (var item in Model) {
<tr data-hidden="@item.Hidden">
<td>
<a name="@item.Code" class="btn btn-link"></a> @Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Code)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>@if (item.Photo!=null) {
<img src="@item.Photo" style="max-height: 4em;" />
}
</td>
<td>
@if (item.Parent!=null) {
<text>
<a href="#@item.ParentCode">@Html.DisplayFor(modelItem => item.Parent)</a>
</text>
}
</td>
<td>
@if (item.SettingsClassName!=null) {
<text>
@item.SettingsClassName
</text>
}
</td>
<td>
@Html.DisplayFor(modelItem => item.Children)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Code" class="btn btn-default">Edit</a>
<a asp-action="Details" asp-route-id="@item.Code" class="btn btn-success">Details</a>
<a asp-action="Delete" asp-route-id="@item.Code" class="btn btn-danger">Delete</a>
</td>
</tr>
}
</table>