diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index d09866d6..f36ed1dc 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -170,8 +170,8 @@ else
@@ -229,7 +229,7 @@ else _clientsecret = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientSecret", ""); _metadata = SettingService.GetSetting(settings, "OpenIdConnectOptions:MetadataAddress", ""); _logouturl = SettingService.GetSetting(settings, "OpenIdConnectOptions:LogoutUrl", ""); - _allowsitelogin = SettingService.GetSetting(settings, "AllowSiteLogin", "True"); + _allowsitelogin = SettingService.GetSetting(settings, "AllowSiteLogin", "true"); } private List Search(string search) diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 5e9f7d05..09953d86 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Oqtane.Infrastructure; using Oqtane.Models; @@ -15,7 +14,6 @@ using Oqtane.Repository; using System.IO; using System.Collections.Generic; using Oqtane.Security; -using System.Net; using Microsoft.AspNetCore.Http; namespace Oqtane.Extensions @@ -26,22 +24,11 @@ namespace Oqtane.Extensions this OqtaneSiteOptionsBuilder builder) where TAlias : class, IAlias, new() { - builder.WithSiteAuthenticationCore(); builder.WithSiteAuthenticationOptions(); return builder; } - public static OqtaneSiteOptionsBuilder WithSiteAuthenticationCore( - this OqtaneSiteOptionsBuilder builder) - where TAlias : class, IAlias, new() - { - builder.Services.DecorateService>(); - builder.Services.Replace(ServiceDescriptor.Singleton()); - - return builder; - } - public static OqtaneSiteOptionsBuilder WithSiteAuthenticationOptions( this OqtaneSiteOptionsBuilder builder) where TAlias : class, IAlias, new() @@ -75,8 +62,8 @@ namespace Oqtane.Extensions // openid connect events options.Events.OnTokenValidated = OnTokenValidated; - options.Events.OnRedirectToIdentityProvider = OnRedirectToIdentityProvider; options.Events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut; + options.Events.OnAccessDenied = OnAccessDenied; options.Events.OnRemoteFailure = OnRemoteFailure; }); @@ -97,6 +84,7 @@ namespace Oqtane.Extensions var email = context.Principal.FindFirstValue(ClaimTypes.Email); var providerKey = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); var loginProvider = context.HttpContext.GetAlias().SiteSettings["OpenIdConnectOptions:Authority"]; + var alias = context.HttpContext.GetAlias(); var _logger = context.HttpContext.RequestServices.GetRequiredService(); if (email != null) @@ -117,10 +105,10 @@ namespace Oqtane.Extensions if (result.Succeeded) { // add user login - await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, "")); + await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, email)); user = new User(); - user.SiteId = context.HttpContext.GetAlias().SiteId; + user.SiteId = alias.SiteId; user.Username = email; user.DisplayName = email; user.Email = email; @@ -145,11 +133,11 @@ namespace Oqtane.Extensions Capacity = Constants.UserFolderCapacity, IsSystem = true, Permissions = new List - { - new Permission(PermissionNames.Browse, user.UserId, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, user.UserId, true) - }.EncodePermissions() + { + new Permission(PermissionNames.Browse, user.UserId, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, user.UserId, true) + }.EncodePermissions() }); } @@ -210,8 +198,8 @@ namespace Oqtane.Extensions } // add Oqtane claims - List userroles = _userRoles.GetUserRoles(user.UserId, context.HttpContext.GetAlias().SiteId).ToList(); - var identity = UserSecurity.CreateClaimsIdentity(context.HttpContext.GetAlias(), user, userroles); + List userroles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList(); + var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles); principal.AddClaims(identity.Claims); // add provider @@ -224,12 +212,6 @@ namespace Oqtane.Extensions } } - private static Task OnRedirectToIdentityProvider(RedirectContext context) - { - //context.ProtocolMessage.SetParameter("key", "value"); - return Task.CompletedTask; - } - private static Task OnRedirectToIdentityProviderForSignOut(RedirectContext context) { var logoutUrl = context.HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:LogoutUrl", ""); @@ -251,59 +233,25 @@ namespace Oqtane.Extensions return Task.CompletedTask; } - private static Task OnRemoteFailure(RemoteFailureContext context) + private static Task OnAccessDenied(AccessDeniedContext context) { var _logger = context.HttpContext.RequestServices.GetRequiredService(); - _logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Remote Failure {Error}", context.Failure.Message); - context.Response.Redirect(context.Properties.RedirectUri); + _logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Access Denied - User May Have Cancelled Their External Login Attempt"); + // redirect to login page + var alias = context.HttpContext.GetAlias(); + context.Response.Redirect(alias.Path + "/login?returnurl=" + context.Properties.RedirectUri); context.HandleResponse(); return Task.CompletedTask; } - public static bool DecorateService(this IServiceCollection services, params object[] parameters) + private static Task OnRemoteFailure(RemoteFailureContext context) { - var existingService = services.SingleOrDefault(s => s.ServiceType == typeof(TService)); - if (existingService == null) - return false; - - var newService = new ServiceDescriptor(existingService.ServiceType, - sp => - { - TService inner = (TService)ActivatorUtilities.CreateInstance(sp, existingService.ImplementationType!); - - var parameters2 = new object[parameters.Length + 1]; - Array.Copy(parameters, 0, parameters2, 1, parameters.Length); - parameters2[0] = inner; - - return ActivatorUtilities.CreateInstance(sp, parameters2)!; - }, - existingService.Lifetime); - - if (existingService.ImplementationInstance != null) - { - newService = new ServiceDescriptor(existingService.ServiceType, - sp => - { - TService inner = (TService)existingService.ImplementationInstance; - return ActivatorUtilities.CreateInstance(sp, inner, parameters)!; - }, - existingService.Lifetime); - } - else if (existingService.ImplementationFactory != null) - { - newService = new ServiceDescriptor(existingService.ServiceType, - sp => - { - TService inner = (TService)existingService.ImplementationFactory(sp); - return ActivatorUtilities.CreateInstance(sp, inner, parameters)!; - }, - existingService.Lifetime); - } - - services.Remove(existingService); - services.Add(newService); - - return true; + var _logger = context.HttpContext.RequestServices.GetRequiredService(); + _logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Remote Failure - {Error}", context.Failure.Message); + // redirect to original page + context.Response.Redirect(context.Properties.RedirectUri); + context.HandleResponse(); + return Task.CompletedTask; } } } diff --git a/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationSchemeProvider.cs b/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationSchemeProvider.cs deleted file mode 100644 index 359e8183..00000000 --- a/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationSchemeProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Options; - -namespace Oqtane.Infrastructure -{ - internal class SiteAuthenticationSchemeProvider : IAuthenticationSchemeProvider - { - public SiteAuthenticationSchemeProvider(IOptions options) - : this(options, new Dictionary(StringComparer.Ordinal)) - { - } - - public SiteAuthenticationSchemeProvider(IOptions options, IDictionary schemes) - { - _optionsProvider = options; - - _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes)); - _requestHandlers = new List(); - - foreach (var builder in _optionsProvider.Value.Schemes) - { - var scheme = builder.Build(); - AddScheme(scheme); - } - } - - private readonly IOptions _optionsProvider; - private readonly object _lock = new object(); - - private readonly IDictionary _schemes; - private readonly List _requestHandlers; - - private Task GetDefaultSchemeAsync() - => _optionsProvider.Value.DefaultScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultScheme) - : Task.FromResult(null); - - public virtual Task GetDefaultAuthenticateSchemeAsync() - => _optionsProvider.Value.DefaultAuthenticateScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultAuthenticateScheme) - : GetDefaultSchemeAsync(); - - public virtual Task GetDefaultChallengeSchemeAsync() - => _optionsProvider.Value.DefaultChallengeScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultChallengeScheme) - : GetDefaultSchemeAsync(); - - public virtual Task GetDefaultForbidSchemeAsync() - => _optionsProvider.Value.DefaultForbidScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultForbidScheme) - : GetDefaultChallengeSchemeAsync(); - - public virtual Task GetDefaultSignInSchemeAsync() - => _optionsProvider.Value.DefaultSignInScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultSignInScheme) - : GetDefaultSchemeAsync(); - - public virtual Task GetDefaultSignOutSchemeAsync() - => _optionsProvider.Value.DefaultSignOutScheme != null - ? GetSchemeAsync(_optionsProvider.Value.DefaultSignOutScheme) - : GetDefaultSignInSchemeAsync(); - - public virtual Task GetSchemeAsync(string name) - => Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null); - - public virtual Task> GetRequestHandlerSchemesAsync() - => Task.FromResult>(_requestHandlers); - - public virtual void AddScheme(AuthenticationScheme scheme) - { - if (_schemes.ContainsKey(scheme.Name)) - { - throw new InvalidOperationException("Scheme already exists: " + scheme.Name); - } - lock (_lock) - { - if (_schemes.ContainsKey(scheme.Name)) - { - throw new InvalidOperationException("Scheme already exists: " + scheme.Name); - } - if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType)) - { - _requestHandlers.Add(scheme); - } - _schemes[scheme.Name] = scheme; - } - } - - public virtual void RemoveScheme(string name) - { - if (!_schemes.ContainsKey(name)) - { - return; - } - lock (_lock) - { - if (_schemes.ContainsKey(name)) - { - var scheme = _schemes[name]; - _requestHandlers.Remove(scheme); - _schemes.Remove(name); - } - } - } - - public virtual Task> GetAllSchemesAsync() - => Task.FromResult>(_schemes.Values); - } -} diff --git a/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationService.cs b/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationService.cs deleted file mode 100644 index 40ccb519..00000000 --- a/Oqtane.Server/Infrastructure/Internal/SiteAuthenticationService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Oqtane.Extensions; -using Oqtane.Models; -using Oqtane.Shared; - -namespace Oqtane.Infrastructure -{ - internal class SiteAuthenticationService : IAuthenticationService - where TAlias : class, IAlias, new() - { - private readonly IAuthenticationService _inner; - - public SiteAuthenticationService(IAuthenticationService inner) - { - _inner = inner ?? throw new System.ArgumentNullException(nameof(inner)); - } - - private static void AddTenantIdentifierToProperties(HttpContext context, ref AuthenticationProperties properties) - { - // add site identifier to the authentication properties so on the callback we can use it to set context - var alias = context.GetAlias(); - if (alias != null) - { - properties ??= new AuthenticationProperties(); - if (!properties.Items.Keys.Contains(Constants.SiteToken)) - { - properties.Items.Add(Constants.SiteToken, alias.SiteKey); - } - } - } - - public Task AuthenticateAsync(HttpContext context, string scheme) - => _inner.AuthenticateAsync(context, scheme); - - public async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - AddTenantIdentifierToProperties(context, ref properties); - await _inner.ChallengeAsync(context, scheme, properties); - } - - public async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - AddTenantIdentifierToProperties(context, ref properties); - await _inner.ForbidAsync(context, scheme, properties); - } - - public async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) - { - AddTenantIdentifierToProperties(context, ref properties); - await _inner.SignInAsync(context, scheme, principal, properties); - } - - public async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties) - { - AddTenantIdentifierToProperties(context, ref properties); - await _inner.SignOutAsync(context, scheme, properties); - } - } -} diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs index ff16773e..9a06903f 100644 --- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs @@ -33,7 +33,7 @@ namespace Oqtane.Infrastructure alias.SiteSettings = cache.GetOrCreate("sitesettings:" + alias.SiteKey, entry => { var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository; - return settingRepository.GetSettings(EntityNames.Site) + return settingRepository.GetSettings(EntityNames.Site, alias.SiteId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); }); // save alias in HttpContext diff --git a/Oqtane.Server/Security/PrincipalValidator.cs b/Oqtane.Server/Security/PrincipalValidator.cs index 5a45c820..ea668689 100644 --- a/Oqtane.Server/Security/PrincipalValidator.cs +++ b/Oqtane.Server/Security/PrincipalValidator.cs @@ -24,7 +24,7 @@ namespace Oqtane.Security if (alias != null) { // verify principal was authenticated for current tenant - if (context.Principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.AliasId.ToString()) + if (context.Principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid)?.Value != alias.SiteKey) { // tenant agnostic requests must be ignored string path = context.Request.Path.ToString().ToLower(); diff --git a/Oqtane.Shared/Security/UserSecurity.cs b/Oqtane.Shared/Security/UserSecurity.cs index c052e903..7ef1ea55 100644 --- a/Oqtane.Shared/Security/UserSecurity.cs +++ b/Oqtane.Shared/Security/UserSecurity.cs @@ -134,7 +134,7 @@ namespace Oqtane.Security { identity.AddClaim(new Claim(ClaimTypes.Name, user.Username)); identity.AddClaim(new Claim(ClaimTypes.PrimarySid, user.UserId.ToString())); - identity.AddClaim(new Claim(ClaimTypes.GroupSid, alias.AliasId.ToString())); + identity.AddClaim(new Claim(ClaimTypes.GroupSid, alias.SiteKey)); if (user.Roles.Contains(RoleNames.Host)) { // host users are site admins by default