diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 66137c16..e71a7142 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -89,7 +89,11 @@ namespace Oqtane.Extensions private static async Task OnTokenValidated(TokenValidatedContext context) { - var email = context.Principal.Identity.Name; + var email = context.Principal.FindFirstValue(ClaimTypes.Email); + var providerKey = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + var loginProvider = context.HttpContext.GetAlias().SiteSettings["OpenIdConnectOptions:Authority"]; + var _logger = context.HttpContext.RequestServices.GetRequiredService(); + if (email != null) { var _identityUserManager = context.HttpContext.RequestServices.GetRequiredService>(); @@ -107,6 +111,9 @@ namespace Oqtane.Extensions var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss")); if (result.Succeeded) { + // add user login + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, "")); + user = new User(); user.SiteId = context.HttpContext.GetAlias().SiteId; user.Username = email; @@ -157,13 +164,37 @@ namespace Oqtane.Extensions } else { - email = identityuser.UserName; + var logins = await _identityUserManager.GetLoginsAsync(identityuser); + var login = logins.FirstOrDefault(item => item.LoginProvider == loginProvider); + if (login != null) + { + if (login.ProviderKey == providerKey) + { + user = _users.GetUser(identityuser.UserName); + } + else + { + // provider keys do not match + _logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Server Provider Key Does Not Match For User {Email}", email); + } + } + else + { + // add user login + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, identityuser.UserName)); + user = _users.GetUser(identityuser.UserName); + } } // add claims to principal - user = _users.GetUser(email); if (user != null) { + // update user + user.LastLoginOn = DateTime.UtcNow; + user.LastIPAddress = context.HttpContext.Connection.RemoteIpAddress.ToString(); + _users.UpdateUser(user); + _logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "User Login Successful {Username}", user.Username); + var principal = (ClaimsIdentity)context.Principal.Identity; // remove the name claim if it exists in the principal @@ -181,8 +212,7 @@ namespace Oqtane.Extensions } else { - var _logger = context.HttpContext.RequestServices.GetRequiredService(); - _logger.Log(LogLevel.Information, "OqtaneSiteAuthenticationBuilderExtensions", Enums.LogFunction.Security, "OpenId Connect Server Did Not Return An Email For User"); + _logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Server Did Not Return An Email Claim"); } } diff --git a/Oqtane.Server/Infrastructure/LogManager.cs b/Oqtane.Server/Infrastructure/LogManager.cs index b75a69f0..949d01b9 100644 --- a/Oqtane.Server/Infrastructure/LogManager.cs +++ b/Oqtane.Server/Infrastructure/LogManager.cs @@ -34,17 +34,17 @@ namespace Oqtane.Infrastructure public void Log(LogLevel level, object @class, LogFunction function, string message, params object[] args) { - Log(-1, level, @class.GetType().AssemblyQualifiedName, function, null, message, args); + Log(-1, level, @class, function, null, message, args); } public void Log(LogLevel level, object @class, LogFunction function, Exception exception, string message, params object[] args) { - Log(-1, level, @class.GetType().AssemblyQualifiedName, function, exception, message, args); + Log(-1, level, @class, function, exception, message, args); } public void Log(int siteId, LogLevel level, object @class, LogFunction function, string message, params object[] args) { - Log(siteId, level, @class.GetType().AssemblyQualifiedName, function, null, message, args); + Log(siteId, level, @class, function, null, message, args); } public void Log(int siteId, LogLevel level, object @class, LogFunction function, Exception exception, string message, params object[] args) @@ -80,8 +80,8 @@ namespace Oqtane.Infrastructure } } - Type type = Type.GetType(@class.ToString()); - if (type != null) + Type type = @class.GetType(); + if (type != null && type != typeof(string)) { log.Category = type.AssemblyQualifiedName; log.Feature = Utilities.GetTypeNameLastSegment(log.Category, 0); diff --git a/Oqtane.Server/Migrations/EntityBuilders/AspNetUserLoginsEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserLoginsEntityBuilder.cs new file mode 100644 index 00000000..8b176436 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserLoginsEntityBuilder.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global + +namespace Oqtane.Migrations.EntityBuilders +{ + public class AspNetUserLoginsEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "AspNetUserLogins"; + private readonly PrimaryKey _primaryKey = new("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + private readonly ForeignKey _foreignKey = new("FK_AspNetUserLogins_AspNetUsers_UserId", x => x.UserId, "AspNetUsers", "Id", ReferentialAction.Cascade); + + public AspNetUserLoginsEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_foreignKey); + } + + protected override AspNetUserLoginsEntityBuilder BuildTable(ColumnsBuilder table) + { + LoginProvider = AddStringColumn(table, "LoginProvider", 450); + ProviderKey = AddStringColumn(table, "ProviderKey", 450); + ProviderDisplayName = AddMaxStringColumn(table, "ProviderDisplayName", true); + UserId = AddStringColumn(table, "UserId", 450); + return this; + } + + public OperationBuilder LoginProvider { get; set; } + + public OperationBuilder ProviderKey { get; set; } + + public OperationBuilder ProviderDisplayName { get; set; } + + public OperationBuilder UserId { get; set; } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/03010003_AddAspNetUserLogins.cs b/Oqtane.Server/Migrations/Tenant/03010003_AddAspNetUserLogins.cs new file mode 100644 index 00000000..eaff69c2 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/03010003_AddAspNetUserLogins.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.03.01.00.03")] + public class AddAspNetUserLogins : MultiDatabaseMigration + { + public AddAspNetUserLogins(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var aspNetUserLoginsEntityBuilder = new AspNetUserLoginsEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUserLoginsEntityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var aspNetUserLoginsEntityBuilder = new AspNetUserLoginsEntityBuilder(migrationBuilder, ActiveDatabase); + aspNetUserLoginsEntityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 44a8bf2b..119d3ab5 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -16,9 +16,6 @@ using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using System.Threading.Tasks; namespace Oqtane {