5 Commits

Author SHA1 Message Date
984b76b170 Externl Login
Some checks failed
Dotnet build and test / log-the-inputs (push) Successful in 6s
Dotnet build and test / build (push) Failing after 1m26s
2025-07-10 15:19:28 +01:00
19a3ba6f87 config the issuer url 2025-07-10 09:16:58 +01:00
a39f39c692 MEF 2025-07-10 08:38:38 +01:00
e870271fe4 interaction needed 2025-07-09 15:17:55 +01:00
080578c101 noPublishColumn 2025-07-09 13:38:36 +01:00
30 changed files with 4087 additions and 278 deletions

5
.vscode/launch.json vendored
View File

@ -109,6 +109,11 @@
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"presentation": {
"hidden": false,
"group": "run",
"order": 1
}
},
{

View File

@ -16,7 +16,7 @@ namespace Yavsc.Helpers
public static string GetUserName(this ClaimsPrincipal user)
{
return user.FindFirstValue(ClaimTypes.Name);
return user.FindFirstValue("name");
}
public static bool IsSignedIn(this ClaimsPrincipal user)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Yavsc.Migrations
{
/// <inheritdoc />
public partial class noPublishColumn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Publish",
table: "BlogSpot");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Publish",
table: "BlogSpot",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

View File

@ -776,9 +776,6 @@ namespace Yavsc.Migrations
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<bool>("Publish")
.HasColumnType("boolean");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(1024)

View File

@ -36,6 +36,7 @@ namespace Yavsc.Models
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Yavsc.Abstract.Models.Messaging;
using Org.BouncyCastle.Asn1.Crmf;
using Microsoft.AspNetCore.Identity;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
@ -85,8 +86,7 @@ namespace Yavsc.Models
}
builder.Entity<Activity>().Property(a => a.ParentCode).IsRequired(false);
//builder.Entity<BlogPost>().HasOne(p => p.Author).WithMany(a => a.Posts);
builder.Entity<IdentityUserLogin<String>>().HasKey(i=> new { i.LoginProvider, i.UserId, i.ProviderKey });
}
// this is not a failback procedure.
@ -301,6 +301,6 @@ namespace Yavsc.Models
public DbSet<Scope> Scopes { get; set; }
public DbSet<BlogSpotPublication> blogSpotPublications{ get; set; }
public DbSet<IdentityUserLogin<String>> AspNetUserLogins { get; set; }
}
}

View File

@ -0,0 +1,33 @@
namespace Yavsc.Models.Auth
{
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations.Schema;
public class YaIdentityUserLogin
{
/// <summary>
/// Gets or sets the login provider for the login (e.g. facebook, google)
/// </summary>
public virtual string LoginProvider { get; set; } = default!;
/// <summary>
/// Gets or sets the unique provider identifier for this login.
/// </summary>
public virtual string ProviderKey { get; set; } = default!;
/// <summary>
/// Gets or sets the friendly name used in a UI for this login.
/// </summary>
public virtual string? ProviderDisplayName { get; set; }
/// <summary>
/// Gets or sets the primary key of the user associated with this login.
/// </summary>
public String UserId { get; set; } = default!;
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
}
}

View File

@ -113,7 +113,7 @@ public class BlogSpotService
_context.SaveChanges(user.GetUserId());
}
public async Task<IEnumerable<IGrouping<string, IBlogPost>>> IndexByTitle(ClaimsPrincipal user, string id, int skip = 0, int take = 25)
public async Task<IEnumerable<IBlogPost>> Index(ClaimsPrincipal user, string id, int skip = 0, int take = 25)
{
IEnumerable<IBlogPost> posts;
@ -149,9 +149,8 @@ public class BlogSpotService
.Select(p => p.BlogPost).ToArray();
}
var data = posts.OrderByDescending(p => p.DateCreated);
var grouped = data.GroupBy(p => p.Title).Skip(skip).Take(take);
return grouped;
var data = posts.OrderByDescending(p => p.DateModified);
return data;
}
public async Task Delete(ClaimsPrincipal user, long id)
@ -174,7 +173,7 @@ public class BlogSpotService
return _context.UserPosts(posterId, readerId);
}
public object? ByTitle(string title)
public object? GetTitle(string title)
{
return _context.BlogSpot.Include(
b => b.Author
@ -190,4 +189,5 @@ public class BlogSpotService
.Include(b => b.ACL)
.SingleOrDefaultAsync(x => x.Id == value);
}
}

View File

@ -0,0 +1,228 @@
/*
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.Security.Claims;
using IdentityModel;
using IdentityServer8;
using IdentityServer8.Events;
using IdentityServer8.Services;
using IdentityServer8.Stores;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc;
using Yavsc.Extensions;
using Yavsc.Interfaces;
using Yavsc.Models;
namespace IdentityServerHost.Quickstart.UI;
[SecurityHeaders]
[AllowAnonymous]
public class ExternalController : Controller
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clientStore;
private readonly ILogger<ExternalController> _logger;
private readonly IEventService _events;
private IExternalIdentityManager _users;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly ApplicationDbContext _dbContext;
public ExternalController(
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IEventService events,
ILogger<ExternalController> logger,
IExternalIdentityManager externalIdentityProviderManager,
SignInManager<ApplicationUser> signInManager,
ApplicationDbContext dbContext,
RoleManager<IdentityRole> roleManager
)
{
// if the TestUserStore is not in DI, then we'll just use the global users collection
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
_users = externalIdentityProviderManager;
_interaction = interaction;
_clientStore = clientStore;
_logger = logger;
_events = events;
_signInManager = signInManager;
_roleManager = roleManager;
_dbContext = dbContext;
}
/// <summary>
/// initiate roundtrip to external authentication provider
/// </summary>
[HttpGet]
public IActionResult Challenge(string scheme, string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";
// validate returnUrl - either it is a valid OIDC URL or back to a local page
if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
// start challenge and roundtrip the return URL and scheme
var props = new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", scheme },
}
};
return Challenge(props, scheme);
}
/// <summary>
/// Post processing of external authentication
/// </summary>
[HttpGet]
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {@claims}", externalClaims);
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = AutoProvisionUser(provider, providerUserId, claims);
}
// this allows us to collect any additional claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
// issue authentication cookie for user
var isuser = new IdentityServerUser(user.Id)
{
DisplayName = user.UserName,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
//await HttpContext.SignInAsync(user, _roleManager, false, _dbContext);
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// retrieve return URL
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.Id, user.UserName, true, context?.Client.ClientId));
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage("Redirect", returnUrl);
}
}
return Redirect(returnUrl);
}
private async Task<(ApplicationUser user,
string provider,
string providerUserId,
IEnumerable<Claim> claims)>
FindUserFromExternalProvider(AuthenticateResult result)
{
var externalUser = result.Principal;
// try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(ClaimTypes.NameIdentifier) ??
throw new Exception("Unknown userid");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
var provider = result.Properties.Items["scheme"];
var providerUserId = userIdClaim.Value;
// find external user
ApplicationUser? user = await _users.FindByExternaleProviderAsync (provider, providerUserId);
return (user, provider, providerUserId, claims);
}
/// <summary>
/// Register a new user by external id
/// </summary>
/// <param name="provider"></param>
/// <param name="providerUserId"></param>
/// <param name="claims"></param>
/// <returns></returns>
private ApplicationUser AutoProvisionUser(string provider, string providerUserId, IEnumerable<Claim> claims)
{
var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList());
return user;
}
// if the external login is OIDC-based, there are certain things we need to preserve to make logout work
// this will be different for WS-Fed, SAML2p or other protocols
private void ProcessLoginCallback(AuthenticateResult externalResult, List<Claim> localClaims, AuthenticationProperties localSignInProps)
{
// if the external system sent a session id claim, copy it over
// so we can use it for single sign-out
var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId);
if (sid != null)
{
localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value));
}
// if the external provider issued an id_token, we'll keep it for signout
var idToken = externalResult.Properties.GetTokenValue("id_token");
if (idToken != null)
{
localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } });
}
}
}

View File

@ -0,0 +1,22 @@
/*
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.Security.Claims;
using Yavsc.Models;
namespace Yavsc.Interfaces;
public interface IExternalIdentityManager
{
ApplicationUser AutoProvisionUser(string provider, string providerUserId, List<Claim> claims);
Task<ApplicationUser?> FindByExternaleProviderAsync(string provider, string providerUserId);
}

View File

@ -47,8 +47,8 @@ namespace Yavsc.Controllers
await blogSpotService.UserPosts(id, User.GetUserId(),
skip, take));
}
IEnumerable<IGrouping<string,IBlogPost>> byTitle = await this.blogSpotService.IndexByTitle(User, id, skip, take);
return View(byTitle);
IEnumerable<IBlogPost> index = await this.blogSpotService.Index(User, id, skip, take);
return View(index);
}
[Route("~/Title/{id?}")]
@ -56,7 +56,7 @@ namespace Yavsc.Controllers
public IActionResult Title(string id)
{
ViewData["Title"] = id;
return View("Title", blogSpotService.ByTitle(id));
return View("Title", blogSpotService.GetTitle(id));
}
private async Task<IEnumerable<BlogPost>> UserPosts(string userName, int pageLen = 10, int pageNum = 0)

View File

@ -36,6 +36,7 @@ using Microsoft.IdentityModel.Protocols.Configuration;
using IdentityModel;
using System.Security.Claims;
using IdentityServer8.Security;
using Yavsc.Interfaces;
namespace Yavsc.Extensions;
@ -43,57 +44,6 @@ namespace Yavsc.Extensions;
public static class HostingExtensions
{
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
bool enableDirectoryBrowsing = false)
{
var userFilesDirInfo = new DirectoryInfo(Config.SiteSetup.Blog);
AbstractFileSystemHelpers.UserFilesDirName = userFilesDirInfo.FullName;
if (!userFilesDirInfo.Exists) userFilesDirInfo.Create();
Config.UserFilesOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(AbstractFileSystemHelpers.UserFilesDirName),
RequestPath = PathString.FromUriComponent(Constants.UserFilesPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing,
};
Config.UserFilesOptions.EnableDefaultFiles = true;
Config.UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
var avatarsDirInfo = new DirectoryInfo(Config.SiteSetup.Avatars);
if (!avatarsDirInfo.Exists) avatarsDirInfo.Create();
Config.AvatarsDirName = avatarsDirInfo.FullName;
Config.AvatarsOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(Config.AvatarsDirName),
RequestPath = PathString.FromUriComponent(Constants.AvatarsPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing
};
var gitdirinfo = new DirectoryInfo(Config.SiteSetup.GitRepository);
Config.GitDirName = gitdirinfo.FullName;
if (!gitdirinfo.Exists) gitdirinfo.Create();
Config.GitOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(Config.GitDirName),
RequestPath = PathString.FromUriComponent(Constants.GitPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing,
};
Config.GitOptions.DefaultFilesOptions.DefaultFileNames.Add("index.md");
Config.GitOptions.StaticFileOptions.ServeUnknownFileTypes = true;
app.UseFileServer(Config.UserFilesOptions);
app.UseFileServer(Config.AvatarsOptions);
app.UseFileServer(Config.GitOptions);
app.UseStaticFiles();
return app;
}
internal static WebApplication ConfigureWebAppServices(this WebApplicationBuilder builder)
{
IServiceCollection services = LoadConfiguration(builder);
@ -163,6 +113,7 @@ public static class HostingExtensions
AddYavscPolicies(services);
services.AddScoped<IAuthorizationHandler, PermissionHandler>();
services.AddTransient<IExternalIdentityManager, ExternalIdentityManager>();
AddAuthentication(builder);
@ -231,23 +182,13 @@ public static class HostingExtensions
// OAuth2AppSettings
var googleAuthSettings = builder.Configuration.GetSection("Authentication:Google");
string? googleClientFile = builder.Configuration["Authentication:Google:GoogleWebClientJson"];
string? googleServiceAccountJsonFile = builder.Configuration["Authentication:Google:GoogleServiceAccountJson"];
if (googleClientFile != null)
{
Config.GoogleWebClientConfiguration = new ConfigurationBuilder().AddJsonFile(googleClientFile).Build();
}
//LoadGoogleConfig(builder.Configuration);
if (googleServiceAccountJsonFile != null)
{
FileInfo safile = new FileInfo(googleServiceAccountJsonFile);
Config.GServiceAccount = JsonConvert.DeserializeObject<GoogleServiceAccount>(safile.OpenText().ReadToEnd());
}
var services = builder.Services;
_ = services.AddControllersWithViews()
.AddNewtonsoftJson();
LoadGoogleConfig(builder.Configuration);
services.Configure<SiteSettings>(siteSection);
services.Configure<SmtpSettings>(smtpSection);
services.Configure<PayPalSettings>(paypalSection);
@ -276,11 +217,12 @@ public static class HostingExtensions
// set the redirect URI to https://localhost:5001/signin-google
options.ClientId = googleClientId;
options.ClientSecret = googleClientSecret;
});
}
private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder)
{
builder.Services.AddTransient<IProfileService,ProfileService>();
//builder.Services.AddTransient<IProfileService,ProfileService>();
var identityServerBuilder = builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
@ -295,7 +237,7 @@ public static class HostingExtensions
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddProfileService<ProfileService>()
// .AddProfileService<ProfileService>()
.AddAspNetIdentity<ApplicationUser>()
;
if (builder.Environment.IsDevelopment())
@ -414,87 +356,57 @@ public static class HostingExtensions
Config.GServiceAccount = JsonConvert.DeserializeObject<GoogleServiceAccount>(safile.OpenText().ReadToEnd());
}
}
}
public class MyIdentityStore : IUserClaimStore<IdentityUser>
{
public Task AddClaimsAsync(IdentityUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IdentityResult> CreateAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
public Task<IdentityUser?> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IdentityUser?> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string?> GetNormalizedUserNameAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetUserIdAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string?> GetUserNameAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IList<IdentityUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task RemoveClaimsAsync(IdentityUser user, IEnumerable<Claim> 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<IdentityResult> UpdateAsync(IdentityUser user, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
bool enableDirectoryBrowsing = false)
{
var userFilesDirInfo = new DirectoryInfo(Config.SiteSetup.Blog);
AbstractFileSystemHelpers.UserFilesDirName = userFilesDirInfo.FullName;
if (!userFilesDirInfo.Exists) userFilesDirInfo.Create();
Config.UserFilesOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(AbstractFileSystemHelpers.UserFilesDirName),
RequestPath = PathString.FromUriComponent(Constants.UserFilesPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing,
};
Config.UserFilesOptions.EnableDefaultFiles = true;
Config.UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
var avatarsDirInfo = new DirectoryInfo(Config.SiteSetup.Avatars);
if (!avatarsDirInfo.Exists) avatarsDirInfo.Create();
Config.AvatarsDirName = avatarsDirInfo.FullName;
Config.AvatarsOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(Config.AvatarsDirName),
RequestPath = PathString.FromUriComponent(Constants.AvatarsPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing
};
var gitdirinfo = new DirectoryInfo(Config.SiteSetup.GitRepository);
Config.GitDirName = gitdirinfo.FullName;
if (!gitdirinfo.Exists) gitdirinfo.Create();
Config.GitOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(Config.GitDirName),
RequestPath = PathString.FromUriComponent(Constants.GitPath),
EnableDirectoryBrowsing = enableDirectoryBrowsing,
};
Config.GitOptions.DefaultFilesOptions.DefaultFileNames.Add("index.md");
Config.GitOptions.StaticFileOptions.ServeUnknownFileTypes = true;
app.UseFileServer(Config.UserFilesOptions);
app.UseFileServer(Config.AvatarsOptions);
app.UseFileServer(Config.GitOptions);
app.UseStaticFiles();
return app;
}
}

View File

@ -1,5 +1,6 @@
using System.Text.Encodings.Web;
using AsciiDocNet;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Yavsc.Helpers
@ -8,17 +9,28 @@ namespace Yavsc.Helpers
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context.AllAttributes.ContainsName("summary"))
{
var summaryLength = context.AllAttributes["summary"].Value;
}
await base.ProcessAsync(context, output);
var content = await output.GetChildContentAsync();
string text = content.GetContent();
if (string.IsNullOrWhiteSpace(text)) return;
try
{
if (context.AllAttributes.ContainsName("summary"))
{
var summaryLength = context.AllAttributes["summary"].Value;
if (summaryLength is HtmlString sumLenStr)
{
if (int.TryParse(sumLenStr.Value, out var sumLen))
{
if (text.Length > sumLen)
{
text = text.Substring(0, sumLen) + "(...)";
}
}
}
}
Document document = Document.Parse(text);
var html = document.ToHtml(2);
using var stringWriter = new StringWriter();

View File

@ -74,7 +74,7 @@ namespace Yavsc.Helpers
);
return googleLogin;
}
public static async Task<UserCredential> GetGoogleCredential(GoogleAuthSettings googleAuthSettings, IDataStore store, string googleUserLoginKey)
public static async Task<UserCredential> GetGoogleCredential(GoogleAuthSettings googleAuthSettings, IDataStore store, string googleUserLoginKey)
{
if (string.IsNullOrEmpty(googleUserLoginKey))
throw new InvalidOperationException("No Google login");
@ -82,7 +82,7 @@ namespace Yavsc.Helpers
var token = await store.GetAsync<TokenResponse>(googleUserLoginKey);
// token != null
var c = SystemClock.Default;
if (token.IsExpired(c)) {
if (token.IsStale) {
token = await RefreshToken(googleAuthSettings, token);
}
return new UserCredential(flow, googleUserLoginKey, token);

View File

@ -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)
%.css: %.scss
scss $^ > $@

View File

@ -0,0 +1,28 @@
using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using Yavsc.Interfaces;
using Yavsc.Models;
public class ExternalIdentityManager : IExternalIdentityManager
{
private ApplicationDbContext _applicationDbContext;
public ExternalIdentityManager(ApplicationDbContext applicationDbContext)
{
_applicationDbContext = applicationDbContext;
}
public ApplicationUser AutoProvisionUser(string provider, string providerUserId, List<Claim> claims)
{
throw new NotImplementedException();
}
public async Task<ApplicationUser?> FindByExternaleProviderAsync(string provider, string providerUserId)
{
var user = await _applicationDbContext.AspNetUserLogins
.FirstOrDefaultAsync(
i => (i.LoginProvider == provider) && (i.ProviderKey == providerUserId)
);
if (user == null) return null;
return await _applicationDbContext.Users.FirstOrDefaultAsync(u=>u.Id == user.UserId);
}
}

View File

@ -60,10 +60,14 @@ $('#commentValidation').html(
}
</style>
}
<div class="container">
<div class="blogpost">
<h1 class="blogtitle" ismarkdown>@Model.Title</h1>
<img class="blogphoto" alt="" src="@Model.Photo" >
<div class="post">
<div class="float-left">
<img alt="" src="@Model.Photo" >
</div>
<h1 ismarkdown>@Model.Title</h1>
@Html.DisplayFor(m=>m.Author)
<asciidoc>@Html.DisplayFor(m=>m.Content)</asciidoc>
@ -98,4 +102,4 @@ $('#commentValidation').html(
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-link">Edit</a>
}
<a asp-action="Index" class="btn btn-link">Back to List</a>
</div>

View File

@ -1,4 +1,4 @@
@model IEnumerable<IGrouping<string,IBlogPost>>
@model IEnumerable<IBlogPost>
@{
ViewData["Title"] = "Blogs, l'index";
}
@ -43,59 +43,51 @@
<a asp-action="Create">Create a new article</a>
</p>
}
<div class="container">
<table class="table">
<div class="blog">
@{
int maxTextLen = 75;
foreach (var post in Model) {
<div class="post">
@foreach (var group in Model) {
var title = group.Key ?? "@";
string secondclass="";
var first = group.First();
int maxTextLen = 256;
<tr><td colspan="3">
<a asp-action="Title" asp-route-id="@group.Key" >@title</a></td></tr>
@foreach (var item in group) {
var trunked = item.Content?.Length > maxTextLen;
<tr>
<td><a asp-action="Details" asp-route-id="@item.Id" class="bloglink">
<img src="@item.Photo" class="blogphoto"></a>
</td>
<td>
<asciidoc summary="@maxTextLen">@item.Content</asciidoc>
@if (trunked) { <a asp-action="Details" asp-route-id="@item.Id" class="bloglink">...</a> }
<span style="font-size:x-small;">@Html.DisplayFor(m => item.Author)</span>
<span style="font-size:xx-small;">
posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((item.DateModified - item.DateCreated).Minutes > 0){ 
@:- Modifié le @item.DateModified.ToString("dddd d MMM yyyy à H:mm")
})
</span>
</td>
<td>
<ul class="actiongroup">
@if ((await AuthorizationService.AuthorizeAsync(User, item, new ReadPermission())).Succeeded) {
<li>
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-lg">Details</a>
</li>
}
else {
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-lg">Details DEBUG</a>
}
@if ((await AuthorizationService.AuthorizeAsync(User, item, new EditPermission())).Succeeded) {
<li><a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-default">Edit</a>
</li>
<li><a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
</li>
}
</ul>
</td>
</tr>
<div class="float-left" >
<a asp-action="Details" asp-route-id="@post.Id" class="bloglink" style="display: float-left;">
<img src="@post.Photo" >
</a>
</div>
<h3>@post.Title</h3>
<div>
<a asp-action="Details" asp-route-id="@post.Id">
<asciidoc summary="@maxTextLen">@post.Content</asciidoc></a>
<span style="font-size:x-small;">@Html.DisplayFor(m => post.Author)</span>
<span style="font-size:xx-small;">
posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((post.DateModified - post.DateCreated).Minutes > 0){ 
@:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm")
})
</span>
</div>
<div class="actiongroup">
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded)
{
<a asp-action="Details" asp-route-id="@post.Id" class="btn btn-light">Details</a>
}
else
{
<a asp-action="Details" asp-route-id="@post.Id" class="btn btn-light">Details</a>
}
@if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded)
{
<a asp-action="Edit" asp-route-id="@post.Id" class="btn btn-default">Edit</a>
<a asp-action="Delete" asp-route-id="@post.Id" class="btn btn-danger">Delete</a>
}
</div>
</div>
}
}
</table>
</div>
@if(Model.Count()==0){<p>Néant</p>}
</div>

View File

@ -28,7 +28,7 @@
<ul class="actiongroup">
@if ((await AuthorizationService.AuthorizeAsync(User, item, new ReadPermission())).Succeeded) {
<li>
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-lg">Details</a>
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-light">Details</a>
</li>
}
@if ((await AuthorizationService.AuthorizeAsync(User, item, new EditPermission())).Succeeded) {

View File

@ -20,9 +20,9 @@
}
</ul>
<p>
<input type="submit" class="btn btn-lg btn-success" name="submit.Grant" value="Grant" />
<input type="submit" class="btn btn-lg btn-danger" name="submit.Deny" value="Deny" />
<input type="submit" class="btn btn-lg btn-success" name="submit.Login" value="Sign in as different user" />
<input type="submit" class="btn btn-light btn-success" name="submit.Grant" value="Grant" />
<input type="submit" class="btn btn-light btn-danger" name="submit.Deny" value="Deny" />
<input type="submit" class="btn btn-light btn-success" name="submit.Login" value="Sign in as different user" />
</p>
</form>

View File

@ -14,6 +14,6 @@
<input type="hidden" name="@parameter.Key" value="@parameter.Value" />
}
<input class="btn btn-lg btn-success" name="Authorize" type="submit" value="Yeah, sure" />
<input class="btn btn-light btn-success" name="Authorize" type="submit" value="Yeah, sure" />
</form>
</div>
</div>

View File

@ -37,7 +37,7 @@
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-lg btn-success" name="submit.Signin">Login</button>
<button type="submit" class="btn btn-light btn-success" name="submit.Signin">Login</button>
</div>
</div>
<p>

View File

@ -2,17 +2,10 @@
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@{
#nullable enable
string? name = null;
if (Context.User!=null)
{
name = Context.User.GetUserName();
}
}
@if (name!=null)
@if (Context.User?.Identity?.IsAuthenticated ?? false)
{
string userName = User.GetUserName();
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdown04" data-bs-toggle="dropdown" aria-expanded="false">Plateforme</a>
<ul class="dropdown-menu" aria-labelledby="dropdown04">
@ -37,7 +30,7 @@
</ul>
</li>}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdown04" data-bs-toggle="dropdown" aria-expanded="false">Hello @UserManager.GetUserName(User)!</a>
<a class="nav-link dropdown-toggle" href="#" id="dropdown04" data-bs-toggle="dropdown" aria-expanded="false">Hello @userName!</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" asp-controller="Manage" asp-action="Index" title="Manage">
@ -61,3 +54,5 @@ else
<a class="nav-link" asp-controller="Account" asp-action="Login" asp-route-ReturnUrl="~/" >Login</a>
</li>
}
using IdentityServer8.Extensions;
#line default

View File

@ -17,30 +17,31 @@
<PackageReference Include="HigginsSoft.IdentityServer8.AspNetIdentity" Version="8.0.5-preview-net9" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6">
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.7" />
<PackageReference Include="Google.Apis.Compute.v1" Version="1.70.0.3829" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor" Version="2.3.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="9.0.6" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="9.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="AsciiDocNet" Version="1.0.0-alpha6" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Yavsc.Server/Yavsc.Server.csproj" />

View File

@ -30,5 +30,21 @@ input[type='checkbox'] {
min-height: 1em; }
.container {
background-color: #000000cf;
color: #ffffff; }
background-color: #00000040;
color: #fff;
border-radius: 2em; }
.post {
background-color: #000000dd;
color: #d1d1d1;
padding: 2.3em;
border-radius: 2em;
border: solid #441515a4 2pt; }
div.actiongroup {
float: right;
margin: .5em; }
div.float-left {
float: left;
margin: .5em; }

View File

@ -45,6 +45,27 @@ input[type='checkbox'] {
}
.container {
background-color: #000000cf;
color:#ffffff;
background-color: #00000040;
color: #fff;
border-radius: 2em;
}
.post {
background-color: #000000dd;
color:#d1d1d1;
padding: 2.3em;
border-radius: 2em;
border: solid #441515a4 2pt;
}
.actiongroup
{
float: right;
margin:.5em;
}
.float-left
{
float: left;
margin:.5em;
}

View File

@ -13,8 +13,7 @@
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication;
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
JwtSecurityTokenHandler.DefaultMapInboundClaims = true;
var builder = WebApplication.CreateBuilder(args);
@ -28,8 +27,7 @@ builder.Services
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.Authority = builder.Configuration.GetValue<String>("AuthIssuer");
options.ClientId = "mvc";
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.ResponseType = "code";
@ -38,15 +36,14 @@ builder.Services
options.Scope.Add("profile");
options.Scope.Add("scope2");
options.MapInboundClaims = true;
options.ClaimActions.MapUniqueJsonKey("preferred_username","preferred_username");
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

View File

@ -19,26 +19,26 @@
}
<form action="~/Home/GetUserInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Get user info</button>
<button class="btn btn-light btn-warning" type="submit">Get user info</button>
</form>
<form action="~/Home/GetApiCall" method="post">
<button class="btn btn-lg btn-warning" type="submit">Api Call</button>
<button class="btn btn-light btn-warning" type="submit">Api Call</button>
</form>
<form action="~/Home/PostDeviceInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Post device info</button>
<button class="btn btn-light btn-warning" type="submit">Post device info</button>
</form>
<form action="~/Home/PostFiles/?subdir=test" method="post" enctype="multipart/form-data">
Envoyer vers le dossier &quot;test&quot; <input type="file" name="file" multiple/>
<button class="btn btn-lg btn-warning" type="submit">Post files</button>
<button class="btn btn-light btn-warning" type="submit">Post files</button>
</form>
<a class="btn btn-lg btn-danger" href="/signout">Sign out</a>
<a class="btn btn-light btn-danger" href="/signout">Sign out</a>
}
else {
<h1>Welcome, anonymous</h1>
<a class="btn btn-lg btn-success" href="/signin">Sign in</a>
<a class="btn btn-light btn-success" href="/signin">Sign in</a>
}
</div>

View File

@ -6,6 +6,7 @@
}
},
"AllowedHosts": "*",
"AuthIssuer": "https://localhost:5001",
"Kestrel": {
"Endpoints":
{

View File

@ -8,7 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.7" />
</ItemGroup>
<ItemGroup>