Merge branch 'fix/webclient' into release/1.0.0

This commit is contained in:
Paul Schneider
2025-02-18 00:00:25 +00:00
45 changed files with 271 additions and 535 deletions

View File

@ -1,67 +0,0 @@
# Please find the full example project here:
# https://gitlab.com/tobiaskoch/gitlab-ci-example-mono
# see https://hub.docker.com/_/mono/
image: mono:4.6.2.7
stages:
- build
- test
- deploy
before_script:
- . /opt/mono-4.6/mono-env
- export GIT_SSL_NO_VERIFY=true
- curl --insecure -sSL https://lua.pschneider.fr/files/Paul/pub/dnx-install.sh | bash
- DNX_USER_HOME=`pwd -P`/dnx . ./dnx/dnvm/dnvm.sh
- cd src/OAuth.AspNet.Token && dnu restore --ignore-failed-sources
- cd ../OAuth.AspNet.AuthServer && dnu restore --ignore-failed-sources
- cd ../Yavsc.Abstract && dnu restore --ignore-failed-sources
- cd ../Yavsc.Server && dnu restore --ignore-failed-sources
- cd ../Yavsc && dnu restore --ignore-failed-sources
- cd ../cli && dnu restore --ignore-failed-sources
- cd ../../test/yavscTests && dnu restore --ignore-failed-sources
- cd ../..
after_script:
debug:
stage: build
artifacts:
paths:
- test/yavscTests/bin
script:
- cd test/yavscTests
- dnu build
non_reg:
stage: test
artifacts:
paths:
- test/yavscTests/test-results.xml
when: always
script:
- cd test/yavscTests
- cp $yavsc_client_secret_json yavsc-client-secret.json
- ASPNET_ENV=Development dnx test -trait ategory=non_reg -maxthreads 1 -xml test-results.xml
release:
stage: deploy
only:
- vnext
artifacts:
paths:
- binaries/Debug
script:
- make packages
deploy_staging:
stage: deploy
script:
- echo "Deploy to staging server"
- make strip_yavscd
- make packages
- isn push -s $ISNSOURCE -k $NUGETSOURCEAPIKEY src/Yavsc.Abstract/bin/*/*.nupkg
environment:
name: staging
url: https://yavscpre.pschneider.fr

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<apikeys>
</apikeys>
<packageSources>
<add key="opt" value="/opt/nuget/pkgs/" />
<add key="Official NuGet Gallery" value="https://www.nuget.org/api/v2/" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="xamarin-forms-ci" value="https://www.myget.org/F/xamarinforms-ci/api/v2" />
</packageSources> <activePackageSource> <add key="myLocal" value="/home/paul/Nupkgs" /> </activePackageSource>
</configuration>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Machine.Specifications.Runner.Console" version="0.9.0" />
</packages>

View File

@ -1,22 +0,0 @@
language: csharp
os: linux
mono:
- beta
sudo: false
install:
- curl --insecure -sSL https://lua.pschneider.fr/files/Paul/pub/dnx-install.sh | bash
- DNX_USER_HOME=`pwd -P`/dnx . ./dnx/dnvm/dnvm.sh
- cd src/OAuth.AspNet.Token && dnu restore --ignore-failed-sources
- cd ../OAuth.AspNet.AuthServer && dnu restore --ignore-failed-sources
- cd ../Yavsc.Abstract && dnu restore --ignore-failed-sources
- cd ../Yavsc.Server && dnu restore --ignore-failed-sources
- cd ../Yavsc && dnu restore --ignore-failed-sources
- cd ../test
- make restore
- make
- cd ../..
- make
script:
- "dnu build"

View File

@ -1,11 +1,6 @@
{ {
"projects": [
"src",
"test"
],
"sdk": { "sdk": {
"runtime": "dotnet", "runtime": "dotnet",
"version": "8.0.405" "version": "8.0.405"
}, }
"packages": "packages"
} }

View File

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.13" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.13" />

View File

@ -14,6 +14,8 @@ namespace Yavsc.ApiControllers
using Yavsc.Attributes.Validation; using Yavsc.Attributes.Validation;
using System.IO; using System.IO;
using Yavsc.Exceptions; using Yavsc.Exceptions;
using Yavsc.Server.Helpers;
using Yavsc.Abstract.Helpers;
[Authorize,Route("api/fs")] [Authorize,Route("api/fs")]
public partial class FileSystemApiController : Controller public partial class FileSystemApiController : Controller

View File

@ -8,6 +8,7 @@ using Yavsc.Models;
using Yavsc.Models.Messaging; using Yavsc.Models.Messaging;
using Yavsc.Services; using Yavsc.Services;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Yavsc.Server.Helpers;
namespace Yavsc.ApiControllers namespace Yavsc.ApiControllers
{ {

View File

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Yavsc.Helpers; using Yavsc.Api.Helpers;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Models.Workflow; using Yavsc.Models.Workflow;

View File

@ -15,6 +15,7 @@ namespace Yavsc.ApiControllers
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Yavsc.ViewModels.Auth; using Yavsc.ViewModels.Auth;
using Yavsc.Server.Helpers;
[Route("api/bill"), Authorize] [Route("api/bill"), Authorize]
public class BillingController : Controller public class BillingController : Controller

View File

@ -1,15 +1,11 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Models.Account; using Yavsc.Api.Helpers;
using Yavsc.ViewModels.Account; using Yavsc.Server.Helpers;
using Yavsc.Helpers;
using Yavsc.Abstract.Identity;
using System.Diagnostics;
namespace Yavsc.WebApi.Controllers namespace Yavsc.WebApi.Controllers
{ {
@ -17,165 +13,54 @@ namespace Yavsc.WebApi.Controllers
[Authorize("ApiScope")] [Authorize("ApiScope")]
public class ApiAccountController : Controller public class ApiAccountController : Controller
{ {
private UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
readonly ApplicationDbContext _dbContext; readonly ApplicationDbContext _dbContext;
private readonly ILogger _logger; private readonly ILogger _logger;
public ApiAccountController(UserManager<ApplicationUser> userManager, public ApiAccountController(
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
ILoggerFactory loggerFactory, ApplicationDbContext dbContext) ILoggerFactory loggerFactory, ApplicationDbContext dbContext)
{ {
UserManager = userManager;
this.roleManager = roleManager;
_signInManager = signInManager;
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController)); _logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
_dbContext = dbContext; _dbContext = dbContext;
} }
public UserManager<ApplicationUser> UserManager
{
get
{
return _userManager;
}
private set
{
_userManager = value;
}
}
private readonly RoleManager<IdentityRole> roleManager;
// POST api/Account/ChangePassword
public async Task<IActionResult> ChangePassword(ChangePasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = await _userManager.FindByIdAsync(User.GetUserId());
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) {
IdentityResult result = await UserManager.ChangePasswordAsync(user, model.OldPassword,
model.NewPassword);
if (!result.Succeeded)
{
AddErrors("NewPassword",result);
return new BadRequestObjectResult(ModelState);
}
}
return Ok();
}
// POST api/Account/SetPassword
public async Task<IActionResult> SetPassword(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = await _userManager.FindByIdAsync(User.GetUserId());
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) {
IdentityResult result = await UserManager.AddPasswordAsync(user, model.NewPassword);
if (!result.Succeeded)
{
AddErrors ("NewPassword",result);
return new BadRequestObjectResult(ModelState);
}
}
return Ok();
}
// POST api/Account/Register
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
AddErrors ("Register",result);
return new BadRequestObjectResult(ModelState);
}
await _signInManager.SignInAsync(user, isPersistent: false);
return Ok();
}
private void AddErrors(string key, IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(key, error.Description);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
[HttpGet("me")] [HttpGet("me")]
public async Task<IActionResult> Me() public async Task<IActionResult> Me()
{ {
if (User==null) if (User == null)
return new BadRequestObjectResult( return new BadRequestObjectResult(
new { error = "user not found" }); new { error = "user not found" });
var uid = User.GetUserId(); var uid = User.GetUserId();
var userData = await _dbContext.Users
.Include(u=>u.PostalAddress)
.Include(u=>u.AccountBalance)
.FirstAsync(u=>u.Id == uid);
var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email, var userData = await GetUserData(uid);
userData.Avatar ,
userData.PostalAddress, userData.DedicatedGoogleCalendar );
var userRoles = _dbContext.UserRoles.Where(u=>u.UserId == uid).Select(r => r.RoleId).ToArray(); var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email,
userData.Avatar,
userData.PostalAddress, userData.DedicatedGoogleCalendar);
IdentityRole [] roles = _dbContext.Roles.Where(r=>userRoles.Contains(r.Id)).ToArray(); var userRoles = _dbContext.UserRoles.Where(u => u.UserId == uid).Select(r => r.RoleId).ToArray();
user.Roles = roles.Select(r=>r.Name).ToArray(); IdentityRole[] roles = _dbContext.Roles.Where(r => userRoles.Contains(r.Id)).ToArray();
user.Roles = roles.Select(r => r.Name).ToArray();
return Ok(user); return Ok(user);
} }
private async Task<ApplicationUser> GetUserData(string uid)
{
return await _dbContext.Users
.Include(u => u.PostalAddress)
.Include(u => u.AccountBalance)
.FirstAsync(u => u.Id == uid);
}
[HttpGet("myhost")] [HttpGet("myhost")]
public IActionResult MyHost () public IActionResult MyHost ()
{ {
return Ok(new { host = Request.ForHost() }); return Ok(new { host = Request.ForHost() });
} }
/// <summary>
/// Actually only updates the user's name.
/// </summary>
/// <param name="me">MyUpdate containing the new user name </param>
/// <returns>Ok when all is ok.</returns>
[HttpPut("me")]
public async Task<IActionResult> UpdateMe(UserInfo me)
{
if (!ModelState.IsValid) return new BadRequestObjectResult(
new { error = "Specify some valid user update request." });
var user = await _userManager.FindByIdAsync(User.GetUserId());
var result = await _userManager.SetUserNameAsync(user, me.UserName);
if (result.Succeeded)
return Ok();
else return new BadRequestObjectResult(result);
}
/// <summary> /// <summary>
/// Updates the avatar /// Updates the avatar
/// </summary> /// </summary>
@ -184,11 +69,11 @@ namespace Yavsc.WebApi.Controllers
public async Task<IActionResult> SetAvatar() public async Task<IActionResult> SetAvatar()
{ {
var root = User.InitPostToFileSystem(null); var root = User.InitPostToFileSystem(null);
var user = await _userManager.FindByIdAsync(User.GetUserId()); var user = await GetUserData(User.GetUserId());
if (Request.Form.Files.Count!=1) if (Request.Form.Files.Count!=1)
return new BadRequestResult(); return new BadRequestResult();
var info = user.ReceiveAvatar(Request.Form.Files[0]); var info = user.ReceiveAvatar(Request.Form.Files[0]);
await _userManager.UpdateAsync(user); await _dbContext.SaveChangesAsync();
return Ok(info); return Ok(info);
} }

View File

@ -8,7 +8,7 @@ using Yavsc.ViewModels;
using Yavsc.Models; using Yavsc.Models;
using System.Linq; using System.Linq;
namespace Yavsc.Helpers namespace Yavsc.Api.Helpers
{ {
public static class RequestHelpers public static class RequestHelpers
{ {

View File

@ -0,0 +1,17 @@
using System.Security.Claims;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
using Yavsc.Models.Blog;
namespace Yavsc.Api.Helpers
{
public static class UserHelpers
{
public static string GetUserId(this ClaimsPrincipal user)
{
return user.FindFirstValue("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
}
}
}

View File

@ -10,6 +10,7 @@
copies or substantial portions of the Software. copies or substantial portions of the Software.
*/ */
using IdentityModel;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -27,9 +28,11 @@ internal class Program
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var services = builder.Services; var services = builder.Services;
// builder.Services.AddDistributedMemoryCache();
// accepts any access token issued by identity server // accepts any access token issued by identity server
// adds an authorization policy for scope 'api1' // adds an authorization policy for scope 'scope1'
services services
.AddAuthorization(options => .AddAuthorization(options =>
{ {
@ -37,7 +40,7 @@ internal class Program
{ {
policy policy
.RequireAuthenticatedUser() .RequireAuthenticatedUser()
.RequireClaim("scope", "scope2"); .RequireClaim(JwtClaimTypes.Scope, new string[] { "scope2" });
}); });
}) })
.AddCors(options => .AddCors(options =>
@ -45,13 +48,12 @@ internal class Program
// this defines a CORS policy called "default" // this defines a CORS policy called "default"
options.AddPolicy("default", policy => options.AddPolicy("default", policy =>
{ {
policy.WithOrigins("https://localhost:5003" policy.WithOrigins("https://localhost:5003")
,"http://localhost:5002")
.AllowAnyHeader() .AllowAnyHeader()
.AllowAnyMethod(); .AllowAnyMethod();
}); });
}) })
.AddControllersWithViews(); .AddControllers();
// accepts any access token issued by identity server // accepts any access token issued by identity server
var authenticationBuilder = services.AddAuthentication("Bearer") var authenticationBuilder = services.AddAuthentication("Bearer")
@ -62,21 +64,20 @@ internal class Program
options.TokenValidationParameters = options.TokenValidationParameters =
new() { ValidateAudience = false }; new() { ValidateAudience = false };
}); });
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
services.AddScoped<UserManager<ApplicationUser>>();
services.AddSingleton<IConnexionManager, HubConnectionManager>();
services.AddSingleton<ILiveProcessor, LiveProcessor>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
services.AddIdentityApiEndpoints<ApplicationUser>();
builder.Services.AddSession();
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>()
.AddTransient<DbContext>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>();
/*
services.AddSingleton<IConnexionManager, HubConnectionManager>();
services.AddSingleton<ILiveProcessor, LiveProcessor>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
services.AddIdentityApiEndpoints<ApplicationUser>();
services.AddSession();
*/
using (var app = builder.Build()) using (var app = builder.Build())
{ {
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@ -85,18 +86,28 @@ internal class Program
app app
.UseRouting() .UseRouting()
.UseAuthentication() .UseAuthentication()
.UseAuthorization().UseCors("default") .UseAuthorization()
.UseEndpoints(endpoints => .UseCors("default")
{ /* .UseEndpoints(endpoints =>
endpoints.MapDefaultControllerRoute().RequireAuthorization("ApiScope"); {
}); endpoints.MapDefaultControllerRoute()
//app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope"); .RequireAuthorization();
app.MapGet("/identity", (HttpContext context) => })*/
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
).RequireAuthorization("ApiScope");
// app.UseSession(); ;
// app.MapIdentityApi<ApplicationUser>().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(); await app.RunAsync();
}; }
;
} }
} }

View File

@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "https://localhost:5001",
"sslPort": 6001
}
},
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:6001;",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -1,7 +1,7 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Yavsc.Helpers; using Yavsc.Server.Helpers;
namespace Yavsc.Attributes.Validation namespace Yavsc.Attributes.Validation
{ {

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 const string RoleClaimName = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
} }
} }

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using Yavsc.ViewModels.UserFiles; using Yavsc.ViewModels.UserFiles;
namespace Yavsc.Helpers namespace Yavsc.Server.Helpers
{ {
public static class AbstractFileSystemHelpers public static class AbstractFileSystemHelpers
{ {

View File

@ -1,4 +1,4 @@
namespace Yavsc.Helpers namespace Yavsc.Abstract.Helpers
{ {
public enum ErrorCode { public enum ErrorCode {
NotFound, NotFound,

View File

@ -2,7 +2,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Yavsc.Abstract.FileSystem; using Yavsc.Abstract.FileSystem;
using Yavsc.Helpers; using Yavsc.Server.Helpers;
namespace Yavsc.ViewModels.UserFiles namespace Yavsc.ViewModels.UserFiles
{ {

View File

@ -9,8 +9,5 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.DataAnnotations" />
</ItemGroup>
</Project> </Project>

View File

@ -47,8 +47,8 @@ public static class Config
public static IEnumerable<ApiScope> ApiScopes => public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[] new ApiScope[]
{ {
new ApiScope("scope1"), new ApiScope("scope1",new string[] {"scope1"}),
new ApiScope("scope2"), new ApiScope("scope2",new string[] {"scope2"}),
}; };
public static IEnumerable<Client> Clients => public static IEnumerable<Client> Clients =>
@ -73,6 +73,7 @@ public static class Config
ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) }, ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
AllowedGrantTypes = GrantTypes.Code, AllowedGrantTypes = GrantTypes.Code,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = { "https://localhost:5003/signin-oidc", RedirectUris = { "https://localhost:5003/signin-oidc",
"http://localhost:5002/signin-oidc" }, "http://localhost:5002/signin-oidc" },
@ -80,7 +81,6 @@ public static class Config
"http://localhost:5002/signout-callback-oidc", "http://localhost:5002/signout-callback-oidc",
"https://localhost:5003/signout-callback-oidc" }, "https://localhost:5003/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = { AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.OpenId,

View File

@ -1,10 +1,7 @@
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq;
using Yavsc.Abstract.FileSystem;
using Yavsc.Billing; using Yavsc.Billing;
using Yavsc.Models.Billing; using Yavsc.Models.Billing;
using Yavsc.Server.Helpers;
using Yavsc.Services; using Yavsc.Services;
namespace Yavsc.Helpers namespace Yavsc.Helpers

View File

@ -11,8 +11,9 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Yavsc.Exceptions; using Yavsc.Exceptions;
using Yavsc.Helpers;
namespace Yavsc.Helpers using Yavsc.Abstract.Helpers;
namespace Yavsc.Server.Helpers
{ {
public static class FileSystemHelpers public static class FileSystemHelpers
{ {

View File

@ -9,7 +9,6 @@ namespace Yavsc.Helpers
{ {
public static class UserHelpers public static class UserHelpers
{ {
public static string GetUserId(this ClaimsPrincipal user) public static string GetUserId(this ClaimsPrincipal user)
{ {
return user.FindFirstValue("sub"); return user.FindFirstValue("sub");

View File

@ -9,16 +9,17 @@ using Yavsc.Models;
namespace Yavsc.Services namespace Yavsc.Services
{ {
public class ProfileService : DefaultProfileService, IProfileService public class ProfileService : IProfileService
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
public ProfileService( public ProfileService(
UserManager<ApplicationUser> userManager, ILogger<DefaultProfileService> logger) : base(logger) UserManager<ApplicationUser> userManager,
ILogger<DefaultProfileService> logger)
{ {
_userManager = userManager; _userManager = userManager;
} }
public async Task<List<Claim>> GetClaimsFromUserAsync( private async Task<List<Claim>> GetClaimsFromUserAsync(
ProfileDataRequestContext context, ProfileDataRequestContext context,
ApplicationUser user) ApplicationUser user)
{ {
@ -30,9 +31,11 @@ namespace Yavsc.Services
foreach (var scope in context.RequestedResources.ParsedScopes) foreach (var scope in context.RequestedResources.ParsedScopes)
{ {
claims.Add(new Claim(JwtClaimTypes.Scope, scope.ParsedName)); if (context.Client.AllowedScopes.Contains(scope.ParsedName))
claimAdds.Add(scope.ParsedName); {
// TODO scope has a ParsedParameter claims.Add(new Claim(JwtClaimTypes.Scope, scope.ParsedName));
claimAdds.Add(scope.ParsedName);
}
} }
if (claimAdds.Contains(JwtClaimTypes.Profile)) if (claimAdds.Contains(JwtClaimTypes.Profile))
@ -54,13 +57,13 @@ namespace Yavsc.Services
var roles = await this._userManager.GetRolesAsync(user); var roles = await this._userManager.GetRolesAsync(user);
if (roles.Count()>0) if (roles.Count()>0)
{ {
claims.Add(new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role",String.Join(" ",roles))); claims.AddRange(roles.Select(r => new Claim(Constants.RoleClaimName, r)));
} }
} }
return claims; return claims;
} }
override public async Task GetProfileDataAsync(ProfileDataRequestContext context) public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{ {
var subjectId = GetSubjectId(context.Subject); var subjectId = GetSubjectId(context.Subject);
if (subjectId==null) return; if (subjectId==null) return;
@ -69,7 +72,7 @@ namespace Yavsc.Services
context.IssuedClaims = await GetClaimsFromUserAsync(context, user); context.IssuedClaims = await GetClaimsFromUserAsync(context, user);
} }
override public async Task IsActiveAsync(IsActiveContext context) public async Task IsActiveAsync(IsActiveContext context)
{ {
string? subjectId = GetSubjectId(context.Subject); string? subjectId = GetSubjectId(context.Subject);
if (subjectId == null) if (subjectId == null)

View File

@ -10,6 +10,7 @@ using Newtonsoft.Json;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Models.FileSystem; using Yavsc.Models.FileSystem;
using Yavsc.Server.Helpers;
namespace Yavsc.ViewModels.Streaming namespace Yavsc.ViewModels.Streaming
{ {

View File

@ -12,6 +12,7 @@ namespace Yavsc.Controllers
using Models.Billing; using Models.Billing;
using Models.Workflow; using Models.Workflow;
using ViewModels.Auth; using ViewModels.Auth;
using Yavsc.Server.Helpers;
[Authorize] [Authorize]
public class EstimateController : Controller public class EstimateController : Controller

View File

@ -10,6 +10,7 @@ namespace Yavsc.Controllers
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Models; using Models;
using ViewModels.FrontOffice; using ViewModels.FrontOffice;
using Yavsc.Server.Helpers;
using Yavsc.Services; using Yavsc.Services;
public class FrontOfficeController : Controller public class FrontOfficeController : Controller

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Server.Helpers;
namespace Yavsc.Controllers namespace Yavsc.Controllers
{ {

View File

@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Google.Apis.Util.Store; using Google.Apis.Util.Store;
using IdentityServer8; using IdentityServer8;
using IdentityServer8.Services; using IdentityServer8.Services;
using IdentityServerHost.Quickstart.UI;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
@ -27,6 +29,7 @@ using Yavsc.Models.Workflow;
using Yavsc.Services; using Yavsc.Services;
using Yavsc.Settings; using Yavsc.Settings;
using Yavsc.ViewModels.Auth; using Yavsc.ViewModels.Auth;
using Yavsc.Server.Helpers;
namespace Yavsc.Extensions; namespace Yavsc.Extensions;
@ -137,30 +140,28 @@ public static class HostingExtensions
{ {
IServiceCollection services = LoadConfiguration(builder); IServiceCollection services = LoadConfiguration(builder);
services.AddRazorPages(); //services.AddRazorPages();
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
AddIdentityDBAndStores(builder).AddDefaultTokenProviders();;
AddIdentityServer(builder);
//services.AddScoped<IProfileService, ProfileService>();
services.AddSession(); services.AddSession();
// TODO .AddServerSideSessionStore<YavscServerSideSessionStore>() // TODO .AddServerSideSessionStore<YavscServerSideSessionStore>()
AddAuthentication(services, builder.Configuration);
// Add the system clock service // Add the system clock service
_ = services.AddSingleton<ISystemClock, SystemClock>(); _ = services.AddSingleton<ISystemClock, SystemClock>();
_ = services.AddSingleton<IConnexionManager, HubConnectionManager>(); _ = services.AddSingleton<IConnexionManager, HubConnectionManager>();
_ = services.AddSingleton<ILiveProcessor, LiveProcessor>(); _ = services.AddSingleton<ILiveProcessor, LiveProcessor>();
_ = services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>(); _ = services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
AddIdentityDBAndStores(builder).AddDefaultTokenProviders();
AddIdentityServer(builder);
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
services.AddMvc(config => services.AddMvc(config =>
{ {
/* var policy = new AuthorizationPolicyBuilder() /* var policy = new AuthorizationPolicyBuilder()
@ -208,6 +209,7 @@ public static class HostingExtensions
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
AddAuthentication(builder);
// accepts any access token issued by identity server // accepts any access token issued by identity server
return builder.Build(); return builder.Build();
@ -297,20 +299,16 @@ public static class HostingExtensions
return services; return services;
} }
private static void AddAuthentication(IServiceCollection services, IConfigurationRoot configurationRoot) private static void AddAuthentication(WebApplicationBuilder builder)
{ {
string? googleClientId = configurationRoot["Authentication:Google:ClientId"]; IServiceCollection services=builder.Services;
IConfigurationRoot configurationRoot=builder.Configuration;
string? googleClientId = configurationRoot["Authentication:Google:ClientId"];
string? googleClientSecret = configurationRoot["Authentication:Google:ClientSecret"]; string? googleClientSecret = configurationRoot["Authentication:Google:ClientSecret"];
var authenticationBuilder = services.AddAuthentication() var authenticationBuilder = services.AddAuthentication();
.AddJwtBearer("Bearer", options =>
{ if (googleClientId!=null && googleClientSecret!=null)
options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
authenticationBuilder.AddGoogle(options => authenticationBuilder.AddGoogle(options =>
{ {
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
@ -324,21 +322,31 @@ public static class HostingExtensions
} }
private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder) private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder)
{ {
var identityServerBuilder = builder.Services.AddIdentityServer() builder.Services.AddTransient<IProfileService,ProfileService>();
var identityServerBuilder = builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://IdentityServer8.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.Clients) .AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>() .AddAspNetIdentity<ApplicationUser>()
.AddJwtBearerClientAuthentication() .AddProfileService<ProfileService>()
// .AddProfileService<ProfileService>() ;
;
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())
{ {
identityServerBuilder.AddDeveloperSigningCredential(); identityServerBuilder.AddDeveloperSigningCredential();
} }
else else
{ {
var key = builder.Configuration["YOUR-KEY-NAME"]; var key = builder.Configuration["YavscSigningCert"];
Debug.Assert(key != null);
var pfxBytes = Convert.FromBase64String(key); var pfxBytes = Convert.FromBase64String(key);
var cert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.MachineKeySet); var cert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.MachineKeySet);
identityServerBuilder.AddSigningCredential(cert); identityServerBuilder.AddSigningCredential(cert);
@ -381,7 +389,7 @@ public static class HostingExtensions
} }
public static WebApplication ConfigurePipeline(this WebApplication app) internal static WebApplication ConfigurePipeline(this WebApplication app)
{ {
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@ -398,13 +406,10 @@ public static class HostingExtensions
app.UseIdentityServer(); app.UseIdentityServer();
app.UseAuthorization(); app.UseAuthorization();
app.UseCors("default"); app.UseCors("default");
app.MapControllerRoute( app.MapDefaultControllerRoute();
name: "default", //pp.MapRazorPages();
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages()
.RequireAuthorization();
app.MapHub<ChatHub>("/chatHub"); app.MapHub<ChatHub>("/chatHub");
app.MapAreaControllerRoute("api", "api", "~/api/{controller}/{action}/{id?}");
ConfigureWorkflow(); ConfigureWorkflow();
var services = app.Services; var services = app.Services;
ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>(); ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>();

View File

@ -0,0 +1,10 @@
using System.Security.Claims;
namespace Yavsc.Helpers
{
public static class UserHelpers
{
}
}

View File

@ -1,27 +1,19 @@
{ {
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": { "iisSettings": {
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:30089", "applicationUrl": "https://localhost:5001",
"sslPort": 44391 "sslPort": 5001
} }
}, },
"profiles": { "profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5172",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "https://localhost:7062;http://localhost:5172", "applicationUrl": "https://localhost:5001;",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@ -1,3 +0,0 @@
Support for ASP.NET Core Identity was added to your project.
For setup and configuration information, see https://go.microsoft.com/fwlink/?linkid=2116645.

View File

@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Server.Helpers;
using Yavsc.Settings; using Yavsc.Settings;
namespace Yavsc; namespace Yavsc;

View File

@ -8,6 +8,7 @@ using Yavsc.ViewModels;
using Yavsc.ViewModels.Gen; using Yavsc.ViewModels.Gen;
using Yavsc.Services; using Yavsc.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Yavsc.Server.Helpers;
namespace Yavsc.ViewComponents namespace Yavsc.ViewComponents
{ {

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks; using System.Threading.Tasks;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Server.Helpers;
using Yavsc.ViewModels.UserFiles; using Yavsc.ViewModels.UserFiles;
namespace Yavsc.ViewComponents namespace Yavsc.ViewComponents

View File

@ -12,14 +12,3 @@
<strong>Request ID:</strong> <code>@Model.RequestId</code> <strong>Request ID:</strong> <code>@Model.RequestId</code>
</p> </p>
} }
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,14 +0,0 @@
{
"name": "yavsc",
"version": "1.0.7",
"description": "Yet Another Very Small Company",
"repository": {
"type": "Git",
"url": "https://github.com/pazof/yavsc"
},
"license": "GPL-3.0",
"devDependencies": {},
"dependencies": {
"paypal-permissions-sdk": "^1.0.10"
}
}

View File

@ -71,12 +71,11 @@ namespace testOauthClient.Controllers
var accessToken = await HttpContext.GetTokenAsync("access_token"); var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false }); var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false });
client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("Accept", "application/json");
client.SetBearerToken(accessToken); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetAsync("https://localhost:6001/identity"); var content = await client.GetAsync("https://localhost:6001/identity");
content.EnsureSuccessStatusCode(); content.EnsureSuccessStatusCode();
var json = await content.Content.ReadAsStreamAsync(); var json = await content.Content.ReadAsStringAsync();
var obj = JsonSerializer.Deserialize<JsonElement>(json); return View("UserInfo", json);
return View("UserInfo", obj.ToString());
} }

View File

@ -1,19 +1,62 @@
using Microsoft.AspNetCore.Authentication.OAuth; /*
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 System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
public class Program JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services
.AddAuthentication(options =>
{ {
public static void Main(string[] args) options.DefaultScheme = "Cookies";
{ options.DefaultChallengeScheme = "oidc";
CreateHostBuilder(args).Build().Run(); })
} .AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
public static IHostBuilder CreateHostBuilder(string[] args) => options.ClientId = "mvc";
Host.CreateDefaultBuilder(args) options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
.ConfigureWebHostDefaults(webBuilder => options.ResponseType = "code";
{
webBuilder.UseStartup<Startup>(); options.Scope.Add("openid");
}); options.Scope.Add("profile");
} options.Scope.Add("scope2");
options.MapInboundClaims = true;
options.ClaimActions.MapUniqueJsonKey("preferred_username","preferred_username");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
options.SaveTokens = true;
});
using (var app = builder.Build())
{
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapDefaultControllerRoute().RequireAuthorization();
await app.RunAsync();
}

View File

@ -1,75 +0,0 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services
.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.ResponseType = "code";
options.Scope.Add("scope2");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.ClaimActions.MapUniqueJsonKey(
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
options.ClaimActions.MapUniqueJsonKey("role",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
options.ClaimActions.MapUniqueJsonKey("roles",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute()
.RequireAuthorization();
});
}
}

View File

@ -1,38 +0,0 @@
{
"folders": [
{
"path": ".vscode"
},
{
"path": "."
},
{
"path": "src/test"
},
{
"path": "src/Yavsc"
},
{
"path": "src/cli"
},
{
"path": "src/GoogleCode"
},
{
"path": "src/OAuth.AspNet.AuthServer"
},
{
"path": "src/OAuth.AspNet.Token"
},
{
"path": "src/Yavsc.Abstract"
},
{
"path": "src/Yavsc.Server"
}
],
"settings": {
"mocha.enabled": true,
"git.ignoreLimitWarning": true
}
}

View File

@ -1,6 +0,0 @@
<WorkspaceItem ctype="Workspace">
<Items>
<Item>ZicMoove.sln</Item>
<Item>yagui/yagui.sln</Item>
</Items>
</WorkspaceItem>