Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/macros
Signed-off-by: Bjarke Berg <mail@bergmania.dk> # Conflicts: # src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
@@ -26,20 +27,42 @@ namespace Umbraco.Extensions
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
}
|
||||
|
||||
//Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd and has the back office session
|
||||
if (user.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated && claimsIdentity.HasClaim(x => x.Type == Constants.Security.SessionIdClaimType))
|
||||
//Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd
|
||||
if (user.Identity is ClaimsIdentity claimsIdentity
|
||||
&& claimsIdentity.IsAuthenticated
|
||||
&& UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var umbracoIdentity))
|
||||
{
|
||||
try
|
||||
{
|
||||
return UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// TODO: Look into this? Why did we do this, see git history and add some notes
|
||||
}
|
||||
return umbracoIdentity;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig authentication
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetRemainingAuthSeconds(this IPrincipal user) => user.GetRemainingAuthSeconds(DateTimeOffset.UtcNow);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig authentication
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="now"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetRemainingAuthSeconds(this IPrincipal user, DateTimeOffset now)
|
||||
{
|
||||
var umbIdentity = user.GetUmbracoIdentity();
|
||||
if (umbIdentity == null) return 0;
|
||||
|
||||
var ticketExpires = umbIdentity.FindFirstValue(Constants.Security.TicketExpiresClaimType);
|
||||
if (ticketExpires.IsNullOrWhiteSpace()) return 0;
|
||||
|
||||
var utcExpired = DateTimeOffset.Parse(ticketExpires, null, DateTimeStyles.RoundtripKind);
|
||||
|
||||
var secondsRemaining = utcExpired.Subtract(now).TotalSeconds;
|
||||
return secondsRemaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,34 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <summary>
|
||||
/// A custom user identity for the Umbraco backoffice
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This inherits from FormsIdentity for backwards compatibility reasons since we still support the forms auth cookie, in v8 we can
|
||||
/// change over to 'pure' asp.net identity and just inherit from ClaimsIdentity.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class UmbracoBackOfficeIdentity : ClaimsIdentity
|
||||
{
|
||||
public static UmbracoBackOfficeIdentity FromClaimsIdentity(ClaimsIdentity identity)
|
||||
public static bool FromClaimsIdentity(ClaimsIdentity identity, out UmbracoBackOfficeIdentity backOfficeIdentity)
|
||||
{
|
||||
return new UmbracoBackOfficeIdentity(identity);
|
||||
//validate that all claims exist
|
||||
foreach (var t in RequiredBackOfficeIdentityClaimTypes)
|
||||
{
|
||||
//if the identity doesn't have the claim, or the claim value is null
|
||||
if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace()))
|
||||
{
|
||||
backOfficeIdentity = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
backOfficeIdentity = new UmbracoBackOfficeIdentity(identity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a back office identity based on an existing claims identity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
private UmbracoBackOfficeIdentity(ClaimsIdentity identity)
|
||||
: base(identity.Claims, Constants.Security.BackOfficeAuthenticationType)
|
||||
{
|
||||
Actor = identity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,22 +48,20 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <param name="startContentNodes"></param>
|
||||
/// <param name="startMediaNodes"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="securityStamp"></param>
|
||||
/// <param name="allowedApps"></param>
|
||||
/// <param name="roles"></param>
|
||||
public UmbracoBackOfficeIdentity(int userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
string sessionId, string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
: base(Enumerable.Empty<Claim>(), Constants.Security.BackOfficeAuthenticationType) //this ctor is used to ensure the IsAuthenticated property is true
|
||||
{
|
||||
if (allowedApps == null) throw new ArgumentNullException(nameof(allowedApps));
|
||||
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
|
||||
if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId));
|
||||
if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp));
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles);
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,43 +76,21 @@ namespace Umbraco.Core.BackOffice
|
||||
/// <param name="startContentNodes"></param>
|
||||
/// <param name="startMediaNodes"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="securityStamp"></param>
|
||||
/// <param name="allowedApps"></param>
|
||||
/// <param name="roles"></param>
|
||||
public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity,
|
||||
int userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
string sessionId, string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
: base(childIdentity.Claims, Constants.Security.BackOfficeAuthenticationType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
|
||||
if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(sessionId)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(sessionId));
|
||||
if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp));
|
||||
Actor = childIdentity;
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, sessionId, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a back office identity based on an existing claims identity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
private UmbracoBackOfficeIdentity(ClaimsIdentity identity)
|
||||
: base(identity.Claims, Constants.Security.BackOfficeAuthenticationType)
|
||||
{
|
||||
Actor = identity;
|
||||
|
||||
//validate that all claims exist
|
||||
foreach (var t in RequiredBackOfficeIdentityClaimTypes)
|
||||
{
|
||||
//if the identity doesn't have the claim, or the claim value is null
|
||||
if (identity.HasClaim(x => x.Type == t) == false || identity.HasClaim(x => x.Type == t && x.Value.IsNullOrWhiteSpace()))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the required claim " + t + " is missing");
|
||||
}
|
||||
}
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
public const string Issuer = Constants.Security.BackOfficeAuthenticationType;
|
||||
@@ -115,8 +109,7 @@ namespace Umbraco.Core.BackOffice
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Constants.Web.SecurityStampClaimType
|
||||
Constants.Security.SecurityStampClaimType
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +117,7 @@ namespace Umbraco.Core.BackOffice
|
||||
/// </summary>
|
||||
private void AddRequiredClaims(int userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
string sessionId, string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
{
|
||||
//This is the id that 'identity' uses to check for the user id
|
||||
if (HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false)
|
||||
@@ -155,13 +148,9 @@ namespace Umbraco.Core.BackOffice
|
||||
if (HasClaim(x => x.Type == ClaimTypes.Locality) == false)
|
||||
AddClaim(new Claim(ClaimTypes.Locality, culture, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == Constants.Security.SessionIdClaimType) == false && SessionId.IsNullOrWhiteSpace() == false)
|
||||
AddClaim(new Claim(Constants.Security.SessionIdClaimType, sessionId, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
//The security stamp claim is also required... this is because this claim type is hard coded
|
||||
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
|
||||
if (HasClaim(x => x.Type == Constants.Web.SecurityStampClaimType) == false)
|
||||
AddClaim(new Claim(Constants.Web.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
//The security stamp claim is also required
|
||||
if (HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false)
|
||||
AddClaim(new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
//Add each app as a separate claim
|
||||
if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null)
|
||||
@@ -211,19 +200,7 @@ namespace Umbraco.Core.BackOffice
|
||||
|
||||
public string Culture => this.FindFirstValue(ClaimTypes.Locality);
|
||||
|
||||
public string SessionId
|
||||
{
|
||||
get => this.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
set
|
||||
{
|
||||
var existing = FindFirst(Constants.Security.SessionIdClaimType);
|
||||
if (existing != null)
|
||||
TryRemoveClaim(existing);
|
||||
AddClaim(new Claim(Constants.Security.SessionIdClaimType, value, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
public string SecurityStamp => this.FindFirstValue(Constants.Web.SecurityStampClaimType);
|
||||
public string SecurityStamp => this.FindFirstValue(Constants.Security.SecurityStampClaimType);
|
||||
|
||||
public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray();
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
|
||||
public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
|
||||
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
|
||||
public const string TicketExpiresClaimType = "http://umbraco.org/2020/06/identity/claims/backoffice/ticketexpires";
|
||||
|
||||
/// <summary>
|
||||
/// The claim type for the ASP.NET Identity security stamp
|
||||
/// </summary>
|
||||
public const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
|
||||
|
||||
public const string AspNetCoreV3PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V3";
|
||||
public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2";
|
||||
|
||||
@@ -40,11 +40,6 @@
|
||||
/// </summary>
|
||||
public const string NoContentRouteName = "umbraco-no-content";
|
||||
|
||||
/// <summary>
|
||||
/// The claim type for the ASP.NET Identity security stamp
|
||||
/// </summary>
|
||||
public const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
|
||||
|
||||
/// <summary>
|
||||
/// The default authentication type used for remembering that 2FA is not needed on next login
|
||||
/// </summary>
|
||||
|
||||
33
src/Umbraco.Core/Security/AuthenticationExtensions.cs
Normal file
33
src/Umbraco.Core/Security/AuthenticationExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.BackOffice;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the thread culture is set based on the back office user's culture
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
public static void EnsureCulture(this IIdentity identity)
|
||||
{
|
||||
if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated)
|
||||
{
|
||||
Thread.CurrentThread.CurrentUICulture =
|
||||
Thread.CurrentThread.CurrentCulture = UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,6 @@ namespace Umbraco.Web.Security
|
||||
/// <value>The current user.</value>
|
||||
IUser CurrentUser { get; }
|
||||
|
||||
[Obsolete("This needs to be removed, ASP.NET Identity should always be used for this operation, this is currently only used in the installer which needs to be updated")]
|
||||
double PerformLogin(int userId);
|
||||
|
||||
[Obsolete("This needs to be removed, ASP.NET Identity should always be used for this operation, this is currently only used in the installer which needs to be updated")]
|
||||
void ClearCurrentLogin();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current user's id.
|
||||
/// </summary>
|
||||
|
||||
@@ -28,8 +28,4 @@
|
||||
<_Parameter1>Umbraco.Tests.Benchmarks</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\AuthenticationExtensions.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,16 +4,12 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.BackOffice;
|
||||
|
||||
namespace Umbraco.Core.BackOffice
|
||||
{
|
||||
public class BackOfficeClaimsPrincipalFactory<TUser> : UserClaimsPrincipalFactory<TUser>
|
||||
where TUser : BackOfficeIdentityUser
|
||||
{
|
||||
private const string _identityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
|
||||
private const string _identityProviderClaimValue = "ASP.NET Identity";
|
||||
|
||||
public BackOfficeClaimsPrincipalFactory(UserManager<TUser> userManager, IOptions<BackOfficeIdentityOptions> optionsAccessor)
|
||||
: base(userManager, optionsAccessor)
|
||||
{
|
||||
@@ -25,9 +21,6 @@ namespace Umbraco.Core.BackOffice
|
||||
|
||||
var baseIdentity = await base.GenerateClaimsAsync(user);
|
||||
|
||||
// Required by ASP.NET 4.x anti-forgery implementation
|
||||
baseIdentity.AddClaim(new Claim(_identityProviderClaimType, _identityProviderClaimValue));
|
||||
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(
|
||||
baseIdentity,
|
||||
user.Id,
|
||||
@@ -36,13 +29,24 @@ namespace Umbraco.Core.BackOffice
|
||||
user.CalculatedContentStartNodeIds,
|
||||
user.CalculatedMediaStartNodeIds,
|
||||
user.Culture,
|
||||
//NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
|
||||
Guid.NewGuid().ToString(),
|
||||
user.SecurityStamp,
|
||||
user.AllowedSections,
|
||||
user.Roles.Select(x => x.RoleId).ToArray());
|
||||
|
||||
return new ClaimsPrincipal(umbracoIdentity);
|
||||
}
|
||||
|
||||
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(TUser user)
|
||||
{
|
||||
// TODO: Have a look at the base implementation https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L79
|
||||
// since it's setting an authentication type that is probably not what we want.
|
||||
// also, this is the method that we should be returning our UmbracoBackOfficeIdentity from , not the method above,
|
||||
// the method above just returns a principal that wraps the identity and we dont use a custom principal,
|
||||
// see https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/UserClaimsPrincipalFactory.cs#L66
|
||||
|
||||
var identity = await base.GenerateClaimsAsync(user);
|
||||
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,11 @@ namespace Umbraco.Core.BackOffice
|
||||
}
|
||||
|
||||
#region What we do not currently support
|
||||
// TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
|
||||
|
||||
// We don't support an IUserClaimStore and don't need to (at least currently)
|
||||
public override bool SupportsUserClaim => false;
|
||||
|
||||
// TODO: Support this
|
||||
// It would be nice to support this but we don't need to currently and that would require IQueryable support for our user service/repository
|
||||
public override bool SupportsQueryableUsers => false;
|
||||
|
||||
/// <summary>
|
||||
@@ -64,8 +65,9 @@ namespace Umbraco.Core.BackOffice
|
||||
/// </summary>
|
||||
public override bool SupportsUserTwoFactor => false;
|
||||
|
||||
// TODO: Support this
|
||||
// We haven't needed to support this yet, though might be necessary for 2FA
|
||||
public override bool SupportsUserPhoneNumber => false;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace Umbraco.Web.Composing.CompositionExtensions
|
||||
// composition.Register<StarterKitInstallStep>(Lifetime.Scope);
|
||||
// composition.Register<StarterKitCleanupStep>(Lifetime.Scope);
|
||||
|
||||
composition.Register<SetUmbracoVersionStep>(Lifetime.Scope);
|
||||
composition.Register<CompleteInstallStep>(Lifetime.Scope);
|
||||
|
||||
composition.Register<InstallStepCollection>();
|
||||
composition.Register<InstallHelper>();
|
||||
composition.RegisterUnique<InstallHelper>();
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Umbraco.Web.Install
|
||||
private static HttpClient _httpClient;
|
||||
private readonly DatabaseBuilder _databaseBuilder;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
private readonly IConnectionStrings _connectionStrings;
|
||||
private readonly IInstallationService _installationService;
|
||||
@@ -33,7 +32,6 @@ namespace Umbraco.Web.Install
|
||||
|
||||
public InstallHelper(DatabaseBuilder databaseBuilder,
|
||||
ILogger logger,
|
||||
IGlobalSettings globalSettings,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IConnectionStrings connectionStrings,
|
||||
IInstallationService installationService,
|
||||
@@ -43,7 +41,6 @@ namespace Umbraco.Web.Install
|
||||
IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_logger = logger;
|
||||
_globalSettings = globalSettings;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
_databaseBuilder = databaseBuilder;
|
||||
_connectionStrings = connectionStrings ?? throw new ArgumentNullException(nameof(connectionStrings));
|
||||
@@ -52,6 +49,9 @@ namespace Umbraco.Web.Install
|
||||
_userAgentProvider = userAgentProvider;
|
||||
_umbracoDatabaseFactory = umbracoDatabaseFactory;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
|
||||
//We need to initialize the type already, as we can't detect later, if the connection string is added on the fly.
|
||||
GetInstallationType();
|
||||
}
|
||||
|
||||
public InstallationType GetInstallationType()
|
||||
@@ -59,7 +59,7 @@ namespace Umbraco.Web.Install
|
||||
return _installationType ?? (_installationType = IsBrandNewInstall ? InstallationType.NewInstall : InstallationType.Upgrade).Value;
|
||||
}
|
||||
|
||||
public async Task InstallStatus(bool isCompleted, string errorMsg)
|
||||
public async Task SetInstallStatusAsync(bool isCompleted, string errorMsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Web.Install
|
||||
// a.OfType<StarterKitInstallStep>().First(),
|
||||
// a.OfType<StarterKitCleanupStep>().First(),
|
||||
|
||||
a.OfType<SetUmbracoVersionStep>().First(),
|
||||
a.OfType<CompleteInstallStep>().First(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
namespace Umbraco.Web.Install.InstallSteps
|
||||
{
|
||||
[InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade,
|
||||
"UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.",
|
||||
PerformsAppRestart = true)]
|
||||
public class CompleteInstallStep : InstallSetupStep<object>
|
||||
{
|
||||
private readonly InstallHelper _installHelper;
|
||||
|
||||
public CompleteInstallStep(InstallHelper installHelper)
|
||||
{
|
||||
_installHelper = installHelper;
|
||||
}
|
||||
|
||||
public override async Task<InstallSetupResult> ExecuteAsync(object model)
|
||||
{
|
||||
//reports the ended install
|
||||
await _installHelper.SetInstallStatusAsync(true, "");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool RequiresExecution(object model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
namespace Umbraco.Web.Install.InstallSteps
|
||||
{
|
||||
[InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade,
|
||||
"UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.",
|
||||
PerformsAppRestart = true)]
|
||||
public class SetUmbracoVersionStep : InstallSetupStep<object>
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly InstallHelper _installHelper;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
|
||||
public SetUmbracoVersionStep(IUmbracoContextAccessor umbracoContextAccessor, InstallHelper installHelper,
|
||||
IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_installHelper = installHelper;
|
||||
_globalSettings = globalSettings;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
}
|
||||
|
||||
public override Task<InstallSetupResult> ExecuteAsync(object model)
|
||||
{
|
||||
//TODO: This needs to be reintroduced, when users are compatible with ASP.NET Core Identity.
|
||||
// var security = _umbracoContextAccessor.GetRequiredUmbracoContext().Security;
|
||||
// if (security.IsAuthenticated() == false && _globalSettings.ConfigurationStatus.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// security.PerformLogin(-1);
|
||||
// }
|
||||
//
|
||||
// if (security.IsAuthenticated())
|
||||
// {
|
||||
// // when a user is already logged in, we need to check whether it's user 'zero'
|
||||
// // which is the legacy super user from v7 - and then we need to actually log the
|
||||
// // true super user in - but before that we need to log off, else audit events
|
||||
// // will try to reference user zero and fail
|
||||
// var userIdAttempt = security.GetUserId();
|
||||
// if (userIdAttempt && userIdAttempt.Result == 0)
|
||||
// {
|
||||
// security.ClearCurrentLogin();
|
||||
// security.PerformLogin(Constants.Security.SuperUserId);
|
||||
// }
|
||||
// }
|
||||
// else if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// // for installs, we need to log the super user in
|
||||
// security.PerformLogin(Constants.Security.SuperUserId);
|
||||
// }
|
||||
|
||||
//reports the ended install
|
||||
_installHelper.InstallStatus(true, "");
|
||||
|
||||
return Task.FromResult<InstallSetupResult>(null);
|
||||
}
|
||||
|
||||
public override bool RequiresExecution(object model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Remove="Install\InstallSteps\CompleteInstallStep.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
[TestFixture]
|
||||
public class BackOfficeClaimsPrincipalFactoryTests
|
||||
{
|
||||
private const int _testUserId = 2;
|
||||
private const string _testUserName = "bob";
|
||||
private const string _testUserGivenName = "Bob";
|
||||
private const string _testUserCulture = "en-US";
|
||||
private const string _testUserSecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177";
|
||||
private BackOfficeIdentityUser _testUser;
|
||||
private Mock<UserManager<BackOfficeIdentityUser>> _mockUserManager;
|
||||
|
||||
@@ -65,52 +70,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
Assert.IsNotNull(umbracoBackOfficeIdentity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateAsync_Should_Create_NameId()
|
||||
[TestCase(ClaimTypes.NameIdentifier, _testUserId)]
|
||||
[TestCase(ClaimTypes.Name, _testUserName)]
|
||||
public async Task CreateAsync_Should_Include_Claim(string expectedClaimType, object expectedClaimValue)
|
||||
{
|
||||
const string expectedClaimType = ClaimTypes.NameIdentifier;
|
||||
var expectedClaimValue = _testUser.Id.ToString();
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateAsync_Should_Create_Name()
|
||||
{
|
||||
const string expectedClaimType = ClaimTypes.Name;
|
||||
var expectedClaimValue = _testUser.UserName;
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateAsync_Should_Create_IdentityProvider()
|
||||
{
|
||||
const string expectedClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
|
||||
const string expectedClaimValue = "ASP.NET Identity";
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue.ToString()));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateAsync_When_SecurityStamp_Supported_Expect_SecurityStamp_Claim()
|
||||
{
|
||||
const string expectedClaimType = Constants.Web.SecurityStampClaimType;
|
||||
const string expectedClaimType = Constants.Security.SecurityStampClaimType;
|
||||
var expectedClaimValue = _testUser.SecurityStamp;
|
||||
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true);
|
||||
@@ -165,12 +140,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
var mockGlobalSettings = new Mock<IGlobalSettings>();
|
||||
mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test");
|
||||
|
||||
_testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List<IReadOnlyUserGroup>())
|
||||
_testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, _testUserId, new List<IReadOnlyUserGroup>())
|
||||
{
|
||||
UserName = "bob",
|
||||
Name = "Bob",
|
||||
UserName = _testUserName,
|
||||
Name = _testUserGivenName,
|
||||
Email = "bob@umbraco.test",
|
||||
SecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177"
|
||||
SecurityStamp = _testUserSecurityStamp,
|
||||
Culture = _testUserCulture
|
||||
};
|
||||
|
||||
_mockUserManager = new Mock<UserManager<BackOfficeIdentityUser>>(new Mock<IUserStore<BackOfficeIdentityUser>>().Object,
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
[Test]
|
||||
public void Create_From_Claims_Identity()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
var claimsIdentity = new ClaimsIdentity(new[]
|
||||
{
|
||||
@@ -31,15 +30,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(Constants.Security.AllowedApplicationsClaimType, "content", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.AllowedApplicationsClaimType, "media", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Web.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
var backofficeIdentity = UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.AreEqual(1234, backofficeIdentity.Id);
|
||||
Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
//Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
|
||||
Assert.AreEqual("testing", backofficeIdentity.Username);
|
||||
Assert.AreEqual("hello world", backofficeIdentity.RealName);
|
||||
@@ -49,7 +48,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
Assert.AreEqual("en-us", backofficeIdentity.Culture);
|
||||
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
|
||||
|
||||
Assert.AreEqual(12, backofficeIdentity.Claims.Count());
|
||||
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -61,13 +60,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity));
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_From_Claims_Identity_Required_Claim_Null()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var claimsIdentity = new ClaimsIdentity(new[]
|
||||
{
|
||||
//null or empty
|
||||
@@ -79,18 +80,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(Constants.Security.AllowedApplicationsClaimType, "content", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.AllowedApplicationsClaimType, "media", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer),
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity));
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Create_With_Claims_And_User_Data()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(new[]
|
||||
{
|
||||
@@ -99,7 +102,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
});
|
||||
|
||||
var identity = new UmbracoBackOfficeIdentity(claimsIdentity,
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", sessionId, sessionId, new[] { "content", "media" }, new[] { "admin" });
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" });
|
||||
|
||||
Assert.AreEqual(12, identity.Claims.Count());
|
||||
}
|
||||
@@ -108,10 +111,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
[Test]
|
||||
public void Clone()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", sessionId, sessionId, new[] { "content", "media" }, new[] { "admin" });
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" });
|
||||
|
||||
var cloned = identity.Clone();
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions
|
||||
{
|
||||
[TestFixture]
|
||||
public class ClaimsPrincipalExtensionsTests
|
||||
{
|
||||
[Test]
|
||||
public void Get_Remaining_Ticket_Seconds()
|
||||
{
|
||||
var backOfficeIdentity = new UmbracoBackOfficeIdentity(-1, "test", "test",
|
||||
Enumerable.Empty<int>(), Enumerable.Empty<int>(), "en-US", Guid.NewGuid().ToString(),
|
||||
Enumerable.Empty<string>(), Enumerable.Empty<string>());
|
||||
var principal = new ClaimsPrincipal(backOfficeIdentity);
|
||||
|
||||
var expireSeconds = 99;
|
||||
var elapsedSeconds = 3;
|
||||
var remainingSeconds = expireSeconds - elapsedSeconds;
|
||||
var now = DateTimeOffset.Now;
|
||||
var then = now.AddSeconds(elapsedSeconds);
|
||||
var expires = now.AddSeconds(expireSeconds).ToString("o");
|
||||
|
||||
backOfficeIdentity.AddClaim(new Claim(
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
expires,
|
||||
ClaimValueTypes.DateTime,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
backOfficeIdentity));
|
||||
|
||||
var ticketRemainingSeconds = principal.GetRemainingAuthSeconds(then);
|
||||
|
||||
Assert.AreEqual(remainingSeconds, ticketRemainingSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security
|
||||
var httpContext = new DefaultHttpContext()
|
||||
{
|
||||
User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity(-1, "test", "test", Enumerable.Empty<int>(), Enumerable.Empty<int>(), "en-US",
|
||||
Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Enumerable.Empty<string>(), Enumerable.Empty<string>()))
|
||||
Guid.NewGuid().ToString(), Enumerable.Empty<string>(), Enumerable.Empty<string>()))
|
||||
};
|
||||
httpContext.Request.IsHttps = true;
|
||||
return httpContext;
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
|
||||
_umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContextAccessor, Mock.Of<IUserService>(), globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Umbraco.Tests.Scoping
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
service,
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Security
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
@@ -58,7 +58,7 @@ namespace Umbraco.Tests.Security
|
||||
var umbCtx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Logging;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
public class UmbracoSecurityStampValidatorTests
|
||||
{
|
||||
private Mock<IOwinContext> _mockOwinContext;
|
||||
private Mock<BackOfficeOwinUserManager> _mockUserManager;
|
||||
private Mock<BackOfficeSignInManager> _mockSignInManager;
|
||||
|
||||
private AuthenticationTicket _testAuthTicket;
|
||||
private CookieAuthenticationOptions _testOptions;
|
||||
private BackOfficeIdentityUser _testUser;
|
||||
private const string _testAuthType = "cookie";
|
||||
|
||||
[Test]
|
||||
public void OnValidateIdentity_When_GetUserIdCallback_Is_Null_Expect_ArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MaxValue, null, null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_Validation_Interval_Not_Met_Expect_No_Op()
|
||||
{
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MaxValue, null, identity => throw new Exception());
|
||||
|
||||
_testAuthTicket.Properties.IssuedUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.AreEqual(_testAuthTicket.Identity, context.Identity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnValidateIdentity_When_Time_To_Validate_But_No_UserManager_Expect_InvalidOperationException()
|
||||
{
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => throw new Exception());
|
||||
|
||||
_mockOwinContext.Setup(x => x.Get<BackOfficeOwinUserManager>(It.IsAny<string>()))
|
||||
.Returns((BackOfficeOwinUserManager) null);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await func(context));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnValidateIdentity_When_Time_To_Validate_But_No_SignInManager_Expect_InvalidOperationException()
|
||||
{
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => throw new Exception());
|
||||
|
||||
_mockOwinContext.Setup(x => x.Get<BackOfficeSignInManager>(It.IsAny<string>()))
|
||||
.Returns((BackOfficeSignInManager) null);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await func(context));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_Time_To_Validate_And_User_No_Longer_Found_Expect_Rejected()
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => userId);
|
||||
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(userId))
|
||||
.ReturnsAsync((BackOfficeIdentityUser) null);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.IsNull(context.Identity);
|
||||
_mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_Time_To_Validate_And_User_Exists_And_Does_Not_Support_SecurityStamps_Expect_Rejected()
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => userId);
|
||||
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser);
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.IsNull(context.Identity);
|
||||
_mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_Time_To_Validate_And_SecurityStamp_Has_Changed_Expect_Rejected()
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => userId);
|
||||
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser);
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true);
|
||||
_mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(Guid.NewGuid().ToString);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.IsNull(context.Identity);
|
||||
_mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_Time_To_Validate_And_SecurityStamp_Has_Not_Changed_Expect_No_Change()
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, null, identity => userId);
|
||||
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser);
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true);
|
||||
_mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.AreEqual(_testAuthTicket.Identity, context.Identity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnValidateIdentity_When_User_Validated_And_RegenerateIdentityCallback_Present_Expect_User_Refreshed()
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var expectedIdentity = new ClaimsIdentity(new List<Claim> {new Claim("sub", "bob")});
|
||||
|
||||
var regenFuncCalled = false;
|
||||
Func<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser, Task<ClaimsIdentity>> regenFunc =
|
||||
(signInManager, userManager, user) =>
|
||||
{
|
||||
regenFuncCalled = true;
|
||||
return Task.FromResult(expectedIdentity);
|
||||
};
|
||||
|
||||
var func = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.MinValue, regenFunc, identity => userId);
|
||||
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser);
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true);
|
||||
_mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp);
|
||||
|
||||
var context = new CookieValidateIdentityContext(
|
||||
_mockOwinContext.Object,
|
||||
_testAuthTicket,
|
||||
_testOptions);
|
||||
|
||||
ClaimsIdentity callbackIdentity = null;
|
||||
_mockOwinContext.Setup(x => x.Authentication.SignIn(context.Properties, It.IsAny<ClaimsIdentity>()))
|
||||
.Callback((AuthenticationProperties props, ClaimsIdentity[] identities) => callbackIdentity = identities.FirstOrDefault())
|
||||
.Verifiable();
|
||||
|
||||
await func(context);
|
||||
|
||||
Assert.True(regenFuncCalled);
|
||||
Assert.AreEqual(expectedIdentity, callbackIdentity);
|
||||
Assert.IsNull(context.Properties.IssuedUtc);
|
||||
Assert.IsNull(context.Properties.ExpiresUtc);
|
||||
|
||||
_mockOwinContext.Verify();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var mockGlobalSettings = new Mock<IGlobalSettings>();
|
||||
mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test");
|
||||
|
||||
_testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List<IReadOnlyUserGroup>())
|
||||
{
|
||||
UserName = "alice",
|
||||
Name = "Alice",
|
||||
Email = "alice@umbraco.test",
|
||||
SecurityStamp = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
_testAuthTicket = new AuthenticationTicket(
|
||||
new ClaimsIdentity(
|
||||
new List<Claim> {new Claim("sub", "alice"), new Claim(Constants.Web.SecurityStampClaimType, _testUser.SecurityStamp)},
|
||||
_testAuthType),
|
||||
new AuthenticationProperties());
|
||||
_testOptions = new CookieAuthenticationOptions { AuthenticationType = _testAuthType };
|
||||
|
||||
_mockUserManager = new Mock<BackOfficeOwinUserManager>(
|
||||
new Mock<IPasswordConfiguration>().Object,
|
||||
new Mock<IIpResolver>().Object,
|
||||
new Mock<IUserStore<BackOfficeIdentityUser>>().Object,
|
||||
null, null, null, null, null, null, null);
|
||||
_mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny<string>())).ReturnsAsync((BackOfficeIdentityUser) null);
|
||||
_mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false);
|
||||
|
||||
_mockSignInManager = new Mock<BackOfficeSignInManager>(
|
||||
_mockUserManager.Object,
|
||||
new Mock<IUserClaimsPrincipalFactory<BackOfficeIdentityUser>>().Object,
|
||||
new Mock<IAuthenticationManager>().Object,
|
||||
new Mock<ILogger>().Object,
|
||||
new Mock<IGlobalSettings>().Object,
|
||||
new Mock<IOwinRequest>().Object);
|
||||
|
||||
_mockOwinContext = new Mock<IOwinContext>();
|
||||
_mockOwinContext.Setup(x => x.Get<BackOfficeOwinUserManager>(It.IsAny<string>()))
|
||||
.Returns(_mockUserManager.Object);
|
||||
_mockOwinContext.Setup(x => x.Get<BackOfficeSignInManager>(It.IsAny<string>()))
|
||||
.Returns(_mockSignInManager.Object);
|
||||
|
||||
_mockOwinContext.Setup(x => x.Authentication.SignOut(It.IsAny<string>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
-1, "admin", "Admin", new []{-1}, new[] { -1 }, "en-US", sessionId, sessionId, new[] { "content", "media", "members" }, new[] { "admin" });
|
||||
-1, "admin", "Admin", new []{-1}, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" });
|
||||
|
||||
return Task.FromResult(new AuthenticationTicket(identity,
|
||||
new AuthenticationProperties()
|
||||
|
||||
@@ -374,8 +374,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
service,
|
||||
new WebSecurity(httpContextAccessor, Factory.GetInstance<IUserService>(),
|
||||
Factory.GetInstance<IGlobalSettings>(), HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings ?? Factory.GetInstance<IGlobalSettings>(),
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -149,7 +149,6 @@
|
||||
<Compile Include="Persistence\Repositories\KeyValueRepositoryTests.cs" />
|
||||
<Compile Include="Security\BackOfficeOwinUserManagerTests.cs" />
|
||||
<Compile Include="Security\OwinDataProtectorTokenProviderTests.cs" />
|
||||
<Compile Include="Security\UmbracoSecurityStampValidatorTests.cs" />
|
||||
<Compile Include="Services\KeyValueServiceTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
|
||||
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
|
||||
|
||||
@@ -60,93 +60,94 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async System.Threading.Tasks.Task GetCurrentUser_Fips()
|
||||
{
|
||||
ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
//setup some mocks
|
||||
var userServiceMock = Mock.Get(ServiceContext.UserService);
|
||||
userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
.Returns(() => null);
|
||||
|
||||
if (Thread.GetDomain().GetData(".appPath") != null)
|
||||
{
|
||||
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar);
|
||||
HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
|
||||
}
|
||||
|
||||
var usersController = new AuthenticationController(
|
||||
new TestUserPasswordConfig(),
|
||||
Factory.GetInstance<IGlobalSettings>(),
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
umbracoContextAccessor,
|
||||
Factory.GetInstance<ISqlContext>(),
|
||||
Factory.GetInstance<ServiceContext>(),
|
||||
Factory.GetInstance<AppCaches>(),
|
||||
Factory.GetInstance<IProfilingLogger>(),
|
||||
Factory.GetInstance<IRuntimeState>(),
|
||||
Factory.GetInstance<UmbracoMapper>(),
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<IRequestAccessor>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.Query<IUser>())
|
||||
.Returns(new Query<IUser>(Current.SqlContext));
|
||||
|
||||
var syntax = new SqlCeSyntaxProvider();
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.SqlSyntax)
|
||||
.Returns(syntax);
|
||||
|
||||
var mappers = new MapperCollection(new[]
|
||||
{
|
||||
new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
|
||||
});
|
||||
|
||||
Mock.Get(Current.SqlContext)
|
||||
.Setup(x => x.Mappers)
|
||||
.Returns(mappers);
|
||||
|
||||
// Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
var typ = typeof(CryptoConfig);
|
||||
var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
|
||||
try
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, true);
|
||||
isFld.SetValue(null, true);
|
||||
}
|
||||
|
||||
var runner = new TestRunner(CtrlFactory);
|
||||
var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
|
||||
|
||||
var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
|
||||
Assert.AreEqual(-1, obj.UserId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!originalFipsValue)
|
||||
{
|
||||
haveFld.SetValue(null, false);
|
||||
isFld.SetValue(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO Reintroduce when moved to .NET Core
|
||||
// [Test]
|
||||
// public async System.Threading.Tasks.Task GetCurrentUser_Fips()
|
||||
// {
|
||||
// ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
// {
|
||||
// //setup some mocks
|
||||
// var userServiceMock = Mock.Get(ServiceContext.UserService);
|
||||
// userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
// .Returns(() => null);
|
||||
//
|
||||
// if (Thread.GetDomain().GetData(".appPath") != null)
|
||||
// {
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter()));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var baseDir = IOHelper.MapPath("").TrimEnd(Path.DirectorySeparatorChar);
|
||||
// HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter()));
|
||||
// }
|
||||
//
|
||||
// var usersController = new AuthenticationController(
|
||||
// new TestUserPasswordConfig(),
|
||||
// Factory.GetInstance<IGlobalSettings>(),
|
||||
// Factory.GetInstance<IHostingEnvironment>(),
|
||||
// umbracoContextAccessor,
|
||||
// Factory.GetInstance<ISqlContext>(),
|
||||
// Factory.GetInstance<ServiceContext>(),
|
||||
// Factory.GetInstance<AppCaches>(),
|
||||
// Factory.GetInstance<IProfilingLogger>(),
|
||||
// Factory.GetInstance<IRuntimeState>(),
|
||||
// Factory.GetInstance<UmbracoMapper>(),
|
||||
// Factory.GetInstance<ISecuritySettings>(),
|
||||
// Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
// Factory.GetInstance<IRequestAccessor>(),
|
||||
// Factory.GetInstance<IEmailSender>()
|
||||
// );
|
||||
// return usersController;
|
||||
// }
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Query<IUser>())
|
||||
// .Returns(new Query<IUser>(Current.SqlContext));
|
||||
//
|
||||
// var syntax = new SqlCeSyntaxProvider();
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.SqlSyntax)
|
||||
// .Returns(syntax);
|
||||
//
|
||||
// var mappers = new MapperCollection(new[]
|
||||
// {
|
||||
// new UserMapper(new Lazy<ISqlContext>(() => Current.SqlContext), new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>())
|
||||
// });
|
||||
//
|
||||
// Mock.Get(Current.SqlContext)
|
||||
// .Setup(x => x.Mappers)
|
||||
// .Returns(mappers);
|
||||
//
|
||||
// // Testing what happens if the system were configured to only use FIPS-compliant algorithms
|
||||
// var typ = typeof(CryptoConfig);
|
||||
// var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic);
|
||||
// var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy");
|
||||
// var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy");
|
||||
// var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, true);
|
||||
// isFld.SetValue(null, true);
|
||||
// }
|
||||
//
|
||||
// var runner = new TestRunner(CtrlFactory);
|
||||
// var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get);
|
||||
//
|
||||
// var obj = JsonConvert.DeserializeObject<UserDetail>(response.Item2);
|
||||
// Assert.AreEqual(-1, obj.UserId);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// if (!originalFipsValue)
|
||||
// {
|
||||
// haveFld.SetValue(null, false);
|
||||
// isFld.SetValue(null, false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +438,7 @@ namespace Umbraco.Tests.Web.Mvc
|
||||
var ctx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
_service,
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, globalSettings, HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.Web
|
||||
var umbCtx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, TestObjects.GetGlobalSettings(), HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
TestObjects.GetGlobalSettings(),
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
@@ -53,7 +53,7 @@ namespace Umbraco.Tests.Web
|
||||
var umbCtx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, TestObjects.GetGlobalSettings(), HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
TestObjects.GetGlobalSettings(),
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
@@ -84,7 +84,7 @@ namespace Umbraco.Tests.Web
|
||||
var umbCtx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new WebSecurity(httpContextAccessor, ServiceContext.UserService, TestObjects.GetGlobalSettings(), HostingEnvironment),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
TestObjects.GetGlobalSettings(),
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
|
||||
@@ -8,11 +8,12 @@ using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Security;
|
||||
@@ -67,6 +68,29 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently logged in Umbraco user
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// We have the attribute [SetAngularAntiForgeryTokens] applied because this method is called initially to determine if the user
|
||||
/// is valid before the login screen is displayed. The Auth cookie can be persisted for up to a day but the csrf cookies are only session
|
||||
/// cookies which means that the auth cookie could be valid but the csrf cookies are no longer there, in that case we need to re-set the csrf cookies.
|
||||
/// </remarks>
|
||||
[UmbracoAuthorize]
|
||||
[TypeFilter(typeof(SetAngularAntiForgeryTokens))]
|
||||
//[CheckIfUserTicketDataIsStale] // TODO: Migrate this, though it will need to be done differently at the cookie auth level
|
||||
public UserDetail GetCurrentUser()
|
||||
{
|
||||
var user = _webSecurity.CurrentUser;
|
||||
var result = _umbracoMapper.Map<UserDetail>(user);
|
||||
|
||||
//set their remaining seconds
|
||||
result.SecondsUntilTimeout = HttpContext.User.GetRemainingAuthSeconds();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a user in
|
||||
/// </summary>
|
||||
|
||||
@@ -16,9 +16,9 @@ using Umbraco.Core.Services;
|
||||
using Umbraco.Core.WebAssets;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.ActionResults;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.WebAssets;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
@@ -50,8 +50,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
IGridConfig gridConfig,
|
||||
BackOfficeServerVariables backOfficeServerVariables,
|
||||
AppCaches appCaches,
|
||||
BackOfficeSignInManager signInManager // TODO: Review this, do we want it/need it or create our own?
|
||||
)
|
||||
BackOfficeSignInManager signInManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_runtimeMinifier = runtimeMinifier;
|
||||
@@ -179,7 +178,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// otherwise process the external login info.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<IActionResult> RenderDefaultOrProcessExternalLoginAsync(
|
||||
private Task<IActionResult> RenderDefaultOrProcessExternalLoginAsync(
|
||||
Func<IActionResult> defaultResponse,
|
||||
Func<IActionResult> externalSignInResponse)
|
||||
{
|
||||
@@ -191,9 +190,9 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
//check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
|
||||
if (ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
|
||||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
|
||||
return defaultResponse();
|
||||
return Task.FromResult(defaultResponse());
|
||||
|
||||
return defaultResponse();
|
||||
return Task.FromResult(defaultResponse());
|
||||
|
||||
//First check if there's external login info, if there's not proceed as normal
|
||||
// TODO: Review this, not sure if this will work as expected until we integrate OAuth
|
||||
|
||||
@@ -13,7 +13,7 @@ using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.WebAssets;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace Umbraco.Extensions
|
||||
backOfficeRoutes.CreateRoutes(endpoints);
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseUmbracoRuntimeMinification();
|
||||
|
||||
// Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
|
||||
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Core.Serialization;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -29,8 +30,9 @@ namespace Umbraco.Extensions
|
||||
services
|
||||
.AddAuthentication(Constants.Security.BackOfficeAuthenticationType)
|
||||
.AddCookie(Constants.Security.BackOfficeAuthenticationType);
|
||||
// TODO: Need to add more cookie options, see https://github.com/dotnet/aspnetcore/blob/3.0/src/Identity/Core/src/IdentityServiceCollectionExtensions.cs#L45
|
||||
|
||||
services.ConfigureOptions<ConfigureUmbracoBackOfficeCookieOptions>();
|
||||
services.ConfigureOptions<ConfigureBackOfficeCookieOptions>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,9 +53,9 @@ namespace Umbraco.Extensions
|
||||
.AddClaimsPrincipalFactory<BackOfficeClaimsPrincipalFactory<BackOfficeIdentityUser>>();
|
||||
|
||||
// Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance
|
||||
services.ConfigureOptions<ConfigureUmbracoBackOfficeIdentityOptions>();
|
||||
//services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<BackOfficeIdentityUser>>();
|
||||
}
|
||||
services.ConfigureOptions<ConfigureBackOfficeIdentityOptions>();
|
||||
services.ConfigureOptions<ConfigureBackOfficeSecurityStampValidatorOptions>();
|
||||
}
|
||||
|
||||
private static IdentityBuilder BuildUmbracoBackOfficeIdentity(this IServiceCollection services)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
@@ -24,10 +25,10 @@ namespace Umbraco.Web.BackOffice.Filters
|
||||
public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
private readonly IBackOfficeAntiforgery _antiforgery;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
|
||||
public ValidateAngularAntiForgeryTokenAttribute(ILogger logger, IAntiforgery antiforgery, ICookieManager cookieManager)
|
||||
public ValidateAngularAntiForgeryTokenAttribute(ILogger logger, IBackOfficeAntiforgery antiforgery, ICookieManager cookieManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_antiforgery = antiforgery;
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Umbraco.Web.BackOffice.Runtime
|
||||
|
||||
composition.RegisterUnique<BackOfficeAreaRoutes>();
|
||||
composition.RegisterUnique<BackOfficeServerVariables>();
|
||||
composition.Register<BackOfficeSessionIdValidator>(Lifetime.Request);
|
||||
composition.Register<BackOfficeSecurityStampValidator>(Lifetime.Request);
|
||||
|
||||
composition.RegisterUnique<IBackOfficeAntiforgery, BackOfficeAntiforgery>();
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// Custom secure format that ensures the Identity in the ticket is <see cref="UmbracoBackOfficeIdentity"/> and not just a ClaimsIdentity
|
||||
/// </summary>
|
||||
// TODO: Unsure if we really need this, there's no real reason why we have a custom Identity instead of just a ClaimsIdentity
|
||||
internal class UmbracoSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
|
||||
internal class BackOfficeSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
|
||||
{
|
||||
private readonly int _loginTimeoutMinutes;
|
||||
private readonly ISecureDataFormat<AuthenticationTicket> _ticketDataFormat;
|
||||
|
||||
public UmbracoSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat<AuthenticationTicket> ticketDataFormat)
|
||||
public BackOfficeSecureDataFormat(int loginTimeoutMinutes, ISecureDataFormat<AuthenticationTicket> ticketDataFormat)
|
||||
{
|
||||
_loginTimeoutMinutes = loginTimeoutMinutes;
|
||||
_ticketDataFormat = ticketDataFormat ?? throw new ArgumentNullException(nameof(ticketDataFormat));
|
||||
@@ -60,18 +60,8 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
return null;
|
||||
}
|
||||
|
||||
UmbracoBackOfficeIdentity identity;
|
||||
|
||||
try
|
||||
{
|
||||
identity = UmbracoBackOfficeIdentity.FromClaimsIdentity((ClaimsIdentity)decrypt.Principal.Identity);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//if it cannot be created return null, will be due to serialization errors in user data most likely due to corrupt cookies or cookies
|
||||
//for previous versions of Umbraco
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity((ClaimsIdentity)decrypt.Principal.Identity, out var identity))
|
||||
return null;
|
||||
}
|
||||
|
||||
//return the ticket with a UmbracoBackOfficeIdentity
|
||||
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), decrypt.Properties, decrypt.AuthenticationScheme);
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A security stamp validator for the back office
|
||||
/// </summary>
|
||||
public class BackOfficeSecurityStampValidator : SecurityStampValidator<BackOfficeIdentityUser>
|
||||
{
|
||||
public BackOfficeSecurityStampValidator(
|
||||
IOptions<BackOfficeSecurityStampValidatorOptions> options,
|
||||
BackOfficeSignInManager signInManager, ISystemClock clock, ILoggerFactory logger)
|
||||
: base(options, signInManager, clock, logger)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom <see cref="SecurityStampValidatorOptions"/> for the back office
|
||||
/// </summary>
|
||||
public class BackOfficeSecurityStampValidatorOptions : SecurityStampValidatorOptions
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,69 +1,94 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager;
|
||||
|
||||
/// <summary>
|
||||
/// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id
|
||||
/// Used to validate a cookie against a user's session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This uses another cookie to track the last checked time which is done for a few reasons:
|
||||
/// * We can't use the user's auth ticket to do this because we'd be re-issuing the auth ticket all of the time and it would never expire
|
||||
/// plus the auth ticket size is much larger than this small value
|
||||
/// * This will execute quite often (every minute per user) and in some cases there might be several requests that end up re-issuing the cookie so the cookie value should be small
|
||||
/// * We want to avoid the user lookup if it's not required so that will only happen when the time diff is great enough in the cookie
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is a scoped/request based object.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static class SessionIdValidator
|
||||
public class BackOfficeSessionIdValidator
|
||||
{
|
||||
public const string CookieName = "UMB_UCONTEXT_C";
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly BackOfficeUserManager _userManager;
|
||||
|
||||
public static async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidateIdentityContext context, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
|
||||
public BackOfficeSessionIdValidator(ISystemClock systemClock, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, BackOfficeUserManager userManager)
|
||||
{
|
||||
if (context.Request.Uri.IsBackOfficeRequest(globalSettings, hostingEnvironment) == false)
|
||||
_systemClock = systemClock;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidatePrincipalContext context)
|
||||
{
|
||||
if (!context.Request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment))
|
||||
return;
|
||||
|
||||
var valid = await ValidateSessionAsync(validateInterval, context.OwinContext, context.Options.CookieManager, context.Options.SystemClock, context.Properties.IssuedUtc, context.Identity, globalSettings);
|
||||
var valid = await ValidateSessionAsync(validateInterval, context.HttpContext, context.Options.CookieManager, _systemClock, context.Properties.IssuedUtc, context.Principal.Identity as ClaimsIdentity);
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
context.RejectIdentity();
|
||||
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
||||
context.RejectPrincipal();
|
||||
await context.HttpContext.SignOutAsync(Constants.Security.BackOfficeAuthenticationType);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> ValidateSessionAsync(
|
||||
private async Task<bool> ValidateSessionAsync(
|
||||
TimeSpan validateInterval,
|
||||
IOwinContext owinCtx,
|
||||
Microsoft.Owin.Infrastructure.ICookieManager cookieManager,
|
||||
HttpContext httpContext,
|
||||
ICookieManager cookieManager,
|
||||
ISystemClock systemClock,
|
||||
DateTimeOffset? authTicketIssueDate,
|
||||
ClaimsIdentity currentIdentity,
|
||||
IGlobalSettings globalSettings)
|
||||
ClaimsIdentity currentIdentity)
|
||||
{
|
||||
if (owinCtx == null) throw new ArgumentNullException("owinCtx");
|
||||
if (cookieManager == null) throw new ArgumentNullException("cookieManager");
|
||||
if (systemClock == null) throw new ArgumentNullException("systemClock");
|
||||
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
|
||||
if (cookieManager == null) throw new ArgumentNullException(nameof(cookieManager));
|
||||
if (systemClock == null) throw new ArgumentNullException(nameof(systemClock));
|
||||
|
||||
if (currentIdentity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTimeOffset? issuedUtc = null;
|
||||
var currentUtc = systemClock.UtcNow;
|
||||
|
||||
//read the last checked time from a custom cookie
|
||||
var lastCheckedCookie = cookieManager.GetRequestCookie(owinCtx, CookieName);
|
||||
var lastCheckedCookie = cookieManager.GetRequestCookie(httpContext, CookieName);
|
||||
|
||||
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(lastCheckedCookie, out parsed))
|
||||
if (DateTimeOffset.TryParse(lastCheckedCookie, out var parsed))
|
||||
{
|
||||
issuedUtc = parsed;
|
||||
}
|
||||
@@ -86,28 +111,24 @@ namespace Umbraco.Web.Security
|
||||
if (validate == false)
|
||||
return true;
|
||||
|
||||
var manager = owinCtx.Get<BackOfficeOwinUserManager>();
|
||||
if (manager == null)
|
||||
return false;
|
||||
|
||||
var userId = currentIdentity.GetUserId();
|
||||
var user = await manager.FindByIdAsync(userId);
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
|
||||
if (await _userManager.ValidateSessionIdAsync(userId, sessionId) == false)
|
||||
return false;
|
||||
|
||||
//we will re-issue the cookie last checked cookie
|
||||
cookieManager.AppendResponseCookie(
|
||||
owinCtx,
|
||||
httpContext,
|
||||
CookieName,
|
||||
DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz"),
|
||||
new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = globalSettings.UseHttps || owinCtx.Request.IsSecure,
|
||||
Secure = _globalSettings.UseHttps || httpContext.Request.IsHttps,
|
||||
Path = "/"
|
||||
});
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Web.Common.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to configure <see cref="CookieAuthenticationOptions"/> for the back office authentication type
|
||||
/// </summary>
|
||||
public class ConfigureBackOfficeCookieOptions : IConfigureNamedOptions<CookieAuthenticationOptions>
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ISecuritySettings _securitySettings;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IDataProtectionProvider _dataProtection;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly BackOfficeSessionIdValidator _sessionIdValidator;
|
||||
|
||||
public ConfigureBackOfficeCookieOptions(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISecuritySettings securitySettings,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
IDataProtectionProvider dataProtection,
|
||||
IRequestCache requestCache,
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
BackOfficeSessionIdValidator sessionIdValidator)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_securitySettings = securitySettings;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_dataProtection = dataProtection;
|
||||
_requestCache = requestCache;
|
||||
_userService = userService;
|
||||
_ipResolver = ipResolver;
|
||||
_sessionIdValidator = sessionIdValidator;
|
||||
}
|
||||
|
||||
public void Configure(string name, CookieAuthenticationOptions options)
|
||||
{
|
||||
if (name != Constants.Security.BackOfficeAuthenticationType) return;
|
||||
Configure(options);
|
||||
}
|
||||
|
||||
public void Configure(CookieAuthenticationOptions options)
|
||||
{
|
||||
options.SlidingExpiration = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes);
|
||||
options.Cookie.Domain = _securitySettings.AuthCookieDomain;
|
||||
options.Cookie.Name = _securitySettings.AuthCookieName;
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = _globalSettings.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
|
||||
options.Cookie.Path = "/";
|
||||
|
||||
// For any redirections that may occur for the back office, they all go to the same path
|
||||
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
|
||||
options.AccessDeniedPath = backOfficePath;
|
||||
options.LoginPath = backOfficePath;
|
||||
options.LogoutPath = backOfficePath;
|
||||
|
||||
options.DataProtectionProvider = _dataProtection;
|
||||
|
||||
// NOTE: This is borrowed directly from aspnetcore source
|
||||
// Note: the purpose for the data protector must remain fixed for interop to work.
|
||||
var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", Constants.Security.BackOfficeAuthenticationType, "v2");
|
||||
var ticketDataFormat = new TicketDataFormat(dataProtector);
|
||||
|
||||
options.TicketDataFormat = new BackOfficeSecureDataFormat(_globalSettings.TimeOutInMinutes, ticketDataFormat);
|
||||
|
||||
//Custom cookie manager so we can filter requests
|
||||
options.CookieManager = new BackOfficeCookieManager(
|
||||
_umbracoContextAccessor,
|
||||
_runtimeState,
|
||||
_hostingEnvironment,
|
||||
_globalSettings,
|
||||
_requestCache);
|
||||
// _explicitPaths); TODO: Implement this once we do OAuth somehow
|
||||
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
// IMPORTANT! If you set any of OnRedirectToLogin, OnRedirectToAccessDenied, OnRedirectToLogout, OnRedirectToReturnUrl
|
||||
// you need to be aware that this will bypass the default behavior of returning the correct status codes for ajax requests and
|
||||
// not redirecting for non-ajax requests. This is because the default behavior is baked into this class here:
|
||||
// https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L58
|
||||
// It would be possible to re-use the default behavior if any of these need to be set but that must be taken into account else
|
||||
// our back office requests will not function correctly. For now we don't need to set/configure any of these callbacks because
|
||||
// the defaults work fine with our setup.
|
||||
|
||||
OnValidatePrincipal = async ctx =>
|
||||
{
|
||||
// We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this)
|
||||
var securityStampValidator = ctx.HttpContext.RequestServices.GetRequiredService<BackOfficeSecurityStampValidator>();
|
||||
// Same goes for the signinmanager
|
||||
var signInManager = ctx.HttpContext.RequestServices.GetRequiredService<BackOfficeSignInManager>();
|
||||
|
||||
var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity == null)
|
||||
{
|
||||
ctx.RejectPrincipal();
|
||||
await signInManager.SignOutAsync();
|
||||
}
|
||||
|
||||
//ensure the thread culture is set
|
||||
backOfficeIdentity.EnsureCulture();
|
||||
|
||||
await EnsureValidSessionId(ctx);
|
||||
await securityStampValidator.ValidateAsync(ctx);
|
||||
|
||||
// add a claim to track when the cookie expires, we use this to track time remaining
|
||||
backOfficeIdentity.AddClaim(new Claim(
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
ctx.Properties.ExpiresUtc.Value.ToString("o"),
|
||||
ClaimValueTypes.DateTime,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
backOfficeIdentity));
|
||||
|
||||
},
|
||||
OnSigningIn = ctx =>
|
||||
{
|
||||
// occurs when sign in is successful but before the ticket is written to the outbound cookie
|
||||
|
||||
var backOfficeIdentity = ctx.Principal.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity != null)
|
||||
{
|
||||
//generate a session id and assign it
|
||||
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
|
||||
var session = _runtimeState.Level == RuntimeLevel.Run
|
||||
? _userService.CreateLoginSession(backOfficeIdentity.Id, _ipResolver.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
//add our session claim
|
||||
backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity));
|
||||
//since it is a cookie-based authentication add that claim
|
||||
backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnSignedIn = ctx =>
|
||||
{
|
||||
// occurs when sign in is successful and after the ticket is written to the outbound cookie
|
||||
|
||||
// When we are signed in with the cookie, assign the principal to the current HttpContext
|
||||
ctx.HttpContext.User = ctx.Principal;
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnSigningOut = ctx =>
|
||||
{
|
||||
//Clear the user's session on sign out
|
||||
// TODO: We need to test this once we have signout functionality, not sure if the httpcontext.user.identity will still be set here
|
||||
if (ctx.HttpContext?.User?.Identity != null)
|
||||
{
|
||||
var claimsIdentity = ctx.HttpContext.User.Identity as ClaimsIdentity;
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession))
|
||||
{
|
||||
_userService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all of our cookies
|
||||
var cookies = new[]
|
||||
{
|
||||
BackOfficeSessionIdValidator.CookieName,
|
||||
_securitySettings.AuthCookieName,
|
||||
Constants.Web.PreviewCookieName,
|
||||
Constants.Security.BackOfficeExternalCookieName
|
||||
};
|
||||
foreach (var cookie in cookies)
|
||||
{
|
||||
ctx.Options.CookieManager.DeleteCookie(ctx.HttpContext, cookie, new CookieOptions
|
||||
{
|
||||
Path = "/"
|
||||
});
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the user has a valid session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// So that we are not overloading the database this throttles it's check to every minute
|
||||
/// </remarks>
|
||||
private async Task EnsureValidSessionId(CookieValidatePrincipalContext context)
|
||||
{
|
||||
if (_runtimeState.Level == RuntimeLevel.Run)
|
||||
await _sessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// <summary>
|
||||
/// Used to configure <see cref="BackOfficeIdentityOptions"/> for the Umbraco Back office
|
||||
/// </summary>
|
||||
public class ConfigureUmbracoBackOfficeIdentityOptions : IConfigureOptions<BackOfficeIdentityOptions>
|
||||
public class ConfigureBackOfficeIdentityOptions : IConfigureOptions<BackOfficeIdentityOptions>
|
||||
{
|
||||
private readonly IUserPasswordConfiguration _userPasswordConfiguration;
|
||||
|
||||
public ConfigureUmbracoBackOfficeIdentityOptions(IUserPasswordConfiguration userPasswordConfiguration)
|
||||
public ConfigureBackOfficeIdentityOptions(IUserPasswordConfiguration userPasswordConfiguration)
|
||||
{
|
||||
_userPasswordConfiguration = userPasswordConfiguration;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
|
||||
options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
|
||||
options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role;
|
||||
options.ClaimsIdentity.SecurityStampClaimType = Constants.Web.SecurityStampClaimType;
|
||||
options.ClaimsIdentity.SecurityStampClaimType = Constants.Security.SecurityStampClaimType;
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the back office security stamp options
|
||||
/// </summary>
|
||||
public class ConfigureBackOfficeSecurityStampValidatorOptions : IConfigureOptions<BackOfficeSecurityStampValidatorOptions>
|
||||
{
|
||||
public void Configure(BackOfficeSecurityStampValidatorOptions options)
|
||||
{
|
||||
options.ValidationInterval = TimeSpan.FromMinutes(30);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to configure <see cref="CookieAuthenticationOptions"/> for the back office authentication type
|
||||
/// </summary>
|
||||
public class ConfigureUmbracoBackOfficeCookieOptions : IConfigureNamedOptions<CookieAuthenticationOptions>
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ISecuritySettings _securitySettings;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IDataProtectionProvider _dataProtection;
|
||||
private readonly IRequestCache _requestCache;
|
||||
|
||||
public ConfigureUmbracoBackOfficeCookieOptions(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISecuritySettings securitySettings,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
IDataProtectionProvider dataProtection,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_securitySettings = securitySettings;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_dataProtection = dataProtection;
|
||||
_requestCache = requestCache;
|
||||
}
|
||||
|
||||
public void Configure(string name, CookieAuthenticationOptions options)
|
||||
{
|
||||
if (name != Constants.Security.BackOfficeAuthenticationType) return;
|
||||
|
||||
options.SlidingExpiration = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes);
|
||||
options.Cookie.Domain = _securitySettings.AuthCookieDomain;
|
||||
options.Cookie.Name = _securitySettings.AuthCookieName;
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = _globalSettings.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
|
||||
options.Cookie.Path = "/";
|
||||
|
||||
options.DataProtectionProvider = _dataProtection;
|
||||
|
||||
// NOTE: This is borrowed directly from aspnetcore source
|
||||
// Note: the purpose for the data protector must remain fixed for interop to work.
|
||||
var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", name, "v2");
|
||||
var ticketDataFormat = new TicketDataFormat(dataProtector);
|
||||
|
||||
options.TicketDataFormat = new UmbracoSecureDataFormat(_globalSettings.TimeOutInMinutes, ticketDataFormat);
|
||||
|
||||
//Custom cookie manager so we can filter requests
|
||||
options.CookieManager = new BackOfficeCookieManager(
|
||||
_umbracoContextAccessor,
|
||||
_runtimeState,
|
||||
_hostingEnvironment,
|
||||
_globalSettings,
|
||||
_requestCache);
|
||||
// _explicitPaths); TODO: Implement this once we do OAuth somehow
|
||||
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
OnSignedIn = ctx =>
|
||||
{
|
||||
// When we are signed in with the cookie, assign the principal to the current HttpContext
|
||||
ctx.HttpContext.User = ctx.Principal;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Configure(CookieAuthenticationOptions options)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.BackOffice.Trees;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
|
||||
@@ -28,12 +28,4 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\ConfigureBackOfficeSecurityStampValidatorOptions.cs" />
|
||||
<Compile Remove="Security\ConfigureBackOfficeIdentityOptions.cs" />
|
||||
<Compile Remove="Security\ConfigureBackOfficeCookieOptions.cs" />
|
||||
<Compile Remove="Security\BackOfficeSessionIdValidator.cs" />
|
||||
<Compile Remove="Security\BackOfficeSecureDataFormat.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -72,6 +72,11 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
app.UseMiddleware<UmbracoRequestMiddleware>();
|
||||
app.UseMiddleware<MiniProfilerMiddleware>();
|
||||
|
||||
// TODO: Both of these need to be done before any endpoints but after UmbracoRequestMiddleware
|
||||
// because they rely on an UmbracoContext. But should they be here?
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
}
|
||||
|
||||
return app;
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
|
||||
namespace Umbraco.Web.Common.Extensions
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
public static bool IsBackOfficeRequest(this HttpRequest request, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
return new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment);
|
||||
}
|
||||
|
||||
public static bool IsClientSideRequest(this HttpRequest request)
|
||||
{
|
||||
return new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest();
|
||||
}
|
||||
|
||||
internal static string ClientCulture(this HttpRequest request)
|
||||
{
|
||||
return request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null;
|
||||
|
||||
@@ -14,11 +14,13 @@ using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Common.ModelBinding;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Install;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
namespace Umbraco.Web.Common.Install
|
||||
{
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
[UmbracoApiController]
|
||||
[TypeFilter(typeof(HttpResponseExceptionFilter))]
|
||||
@@ -30,19 +32,21 @@ namespace Umbraco.Web.Common.Install
|
||||
private readonly DatabaseBuilder _databaseBuilder;
|
||||
private readonly InstallStatusTracker _installStatusTracker;
|
||||
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
|
||||
private readonly BackOfficeSignInManager _backOfficeSignInManager;
|
||||
private readonly InstallStepCollection _installSteps;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProfilingLogger _proflog;
|
||||
|
||||
public InstallApiController(DatabaseBuilder databaseBuilder, IProfilingLogger proflog,
|
||||
InstallHelper installHelper, InstallStepCollection installSteps, InstallStatusTracker installStatusTracker,
|
||||
IUmbracoApplicationLifetime umbracoApplicationLifetime)
|
||||
IUmbracoApplicationLifetime umbracoApplicationLifetime, BackOfficeSignInManager backOfficeSignInManager)
|
||||
{
|
||||
_databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder));
|
||||
_proflog = proflog ?? throw new ArgumentNullException(nameof(proflog));
|
||||
_installSteps = installSteps;
|
||||
_installStatusTracker = installStatusTracker;
|
||||
_umbracoApplicationLifetime = umbracoApplicationLifetime;
|
||||
_backOfficeSignInManager = backOfficeSignInManager;
|
||||
InstallHelper = installHelper;
|
||||
_logger = _proflog;
|
||||
}
|
||||
@@ -85,10 +89,17 @@ namespace Umbraco.Web.Common.Install
|
||||
return starterKits;
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult CompleteInstall()
|
||||
public async Task<ActionResult> CompleteInstall()
|
||||
{
|
||||
// log the super user in if it's a new install
|
||||
var installType = InstallHelper.GetInstallationType();
|
||||
if (installType == InstallationType.NewInstall)
|
||||
{
|
||||
var user = await _backOfficeSignInManager.UserManager.FindByIdAsync(Constants.Security.SuperUserId.ToString());
|
||||
await _backOfficeSignInManager.SignInAsync(user, false);
|
||||
}
|
||||
|
||||
_umbracoApplicationLifetime.Restart();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Umbraco.Web.Common.Install
|
||||
|
||||
ViewData.SetUmbracoVersion(_umbracoVersion.SemanticVersion);
|
||||
|
||||
await _installHelper.InstallStatus(false, "");
|
||||
await _installHelper.SetInstallStatusAsync(false, "");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Serilog.Context;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Common.Middleware
|
||||
{
|
||||
@@ -30,7 +31,7 @@ namespace Umbraco.Web.Common.Middleware
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
// do not process if client-side request
|
||||
if (new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest())
|
||||
if (context.Request.IsClientSideRequest())
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
|
||||
@@ -15,6 +15,9 @@ namespace Umbraco.Web.Common.Middleware
|
||||
/// <summary>
|
||||
/// Manages Umbraco request objects and their lifetime
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is responsible for creating and assigning an <see cref="IUmbracoContext"/>
|
||||
/// </remarks>
|
||||
public class UmbracoRequestMiddleware : IMiddleware
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Common.ModelBinders
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http.Extensions;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Common.Profiler
|
||||
{
|
||||
@@ -70,7 +71,7 @@ namespace Umbraco.Web.Common.Profiler
|
||||
|
||||
private static bool ShouldProfile(HttpRequest request)
|
||||
{
|
||||
if (new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) return false;
|
||||
if (request.IsClientSideRequest()) return false;
|
||||
if (bool.TryParse(request.Query["umbDebug"], out var umbDebug)) return umbDebug;
|
||||
if (bool.TryParse(request.Headers["X-UMB-DEBUG"], out var xUmbDebug)) return xUmbDebug;
|
||||
if (bool.TryParse(request.Cookies["UMB-DEBUG"], out var cUmbDebug)) return cUmbDebug;
|
||||
|
||||
@@ -10,9 +10,14 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
// TODO: There's potential to extract an interface for this for only what we use and put that in Core without aspnetcore refs, but we need to wait till were done with it since there's a bit to implement
|
||||
|
||||
public class BackOfficeSignInManager : SignInManager<BackOfficeIdentityUser>
|
||||
{
|
||||
private readonly BackOfficeUserManager _userManager;
|
||||
@@ -132,21 +137,13 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_userManager.RaiseLoginSuccessEvent(user, user.Id);
|
||||
}
|
||||
else if (result.IsLockedOut)
|
||||
{
|
||||
Logger.LogInformation("Login attempt failed for username {UserName} from IP address {IpAddress}, the user is locked", username, Context.Connection.RemoteIpAddress);
|
||||
}
|
||||
else if (result.RequiresTwoFactor)
|
||||
{
|
||||
Logger.LogInformation("Login attempt requires verification for username {UserName} from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress);
|
||||
}
|
||||
else if (!result.Succeeded || result.IsNotAllowed)
|
||||
{
|
||||
Logger.LogInformation("Login attempt failed for username {UserName} from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
@@ -11,10 +8,10 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Web.Common.Security
|
||||
{
|
||||
// TODO: need to implement this
|
||||
|
||||
public class WebSecurity : IWebSecurity
|
||||
{
|
||||
@@ -23,7 +20,11 @@ namespace Umbraco.Web.Common.Security
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public WebSecurity(IUserService userService, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor)
|
||||
public WebSecurity(
|
||||
IUserService userService,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_userService = userService;
|
||||
_globalSettings = globalSettings;
|
||||
@@ -33,10 +34,7 @@ namespace Umbraco.Web.Common.Security
|
||||
|
||||
private IUser _currentUser;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current user.
|
||||
/// </summary>
|
||||
/// <value>The current user.</value>
|
||||
/// <inheritdoc />
|
||||
public IUser CurrentUser
|
||||
{
|
||||
get
|
||||
@@ -52,6 +50,7 @@ namespace Umbraco.Web.Common.Security
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false)
|
||||
{
|
||||
// check for secure connection
|
||||
@@ -63,37 +62,33 @@ namespace Umbraco.Web.Common.Security
|
||||
return ValidateCurrentUser(throwExceptions);
|
||||
}
|
||||
|
||||
public void ClearCurrentLogin()
|
||||
{
|
||||
//throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Attempt<int> GetUserId()
|
||||
{
|
||||
return Attempt.Succeed(-1);
|
||||
var identity = _httpContextAccessor.GetRequiredHttpContext().GetCurrentIdentity();
|
||||
return identity == null ? Attempt.Fail<int>() : Attempt.Succeed(identity.Id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAuthenticated()
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated && httpContext.GetCurrentIdentity() != null;
|
||||
}
|
||||
|
||||
public double PerformLogin(int userId)
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UserHasSectionAccess(string section, IUser user)
|
||||
{
|
||||
return true;
|
||||
return user.HasSectionAccess(section);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValidateCurrentUser()
|
||||
{
|
||||
return true;
|
||||
return ValidateCurrentUser(false, true) == ValidateRequestAttempt.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true)
|
||||
{
|
||||
//This will first check if the current user is already authenticated - which should be the case in nearly all circumstances
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Security\BackOfficeSignInManager.cs" />
|
||||
<Compile Remove="Macros\UmbracoMacroResult.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -52,11 +52,11 @@ namespace Umbraco.Web
|
||||
_defaultCultureAccessor = defaultCultureAccessor ?? throw new ArgumentNullException(nameof(defaultCultureAccessor));
|
||||
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_uriUtility = uriUtility;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_cookieManager = cookieManager;
|
||||
_requestAccessor = requestAccessor;
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
_uriUtility = uriUtility ?? throw new ArgumentNullException(nameof(uriUtility));
|
||||
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||
_cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager));
|
||||
_requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor));
|
||||
}
|
||||
|
||||
private IUmbracoContext CreateUmbracoContext()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
angular.module('umbraco.interceptors', [])
|
||||
// We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
|
||||
.config(['$httpProvider', function($httpProvider) {
|
||||
.config(['$httpProvider', function ($httpProvider) {
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN';
|
||||
$httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN';
|
||||
|
||||
$httpProvider.interceptors.push('securityInterceptor');
|
||||
$httpProvider.interceptors.push('debugRequestInterceptor');
|
||||
$httpProvider.interceptors.push('requiredHeadersInterceptor');
|
||||
$httpProvider.interceptors.push('doNotPostDollarVariablesOnPostRequestInterceptor');
|
||||
$httpProvider.interceptors.push('cultureRequestInterceptor');
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Used to set debug headers on all requests where necessary
|
||||
* @param {any} $q
|
||||
* @param {any} urlHelper
|
||||
*/
|
||||
function debugRequestInterceptor($q, urlHelper) {
|
||||
return {
|
||||
//dealing with requests:
|
||||
'request': function(config) {
|
||||
var queryStrings = urlHelper.getQueryStringParams();
|
||||
if (queryStrings.umbDebug === "true" || queryStrings.umbdebug === "true") {
|
||||
config.headers["X-UMB-DEBUG"] = "true";
|
||||
}
|
||||
return config;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.interceptors').factory('debugRequestInterceptor', debugRequestInterceptor);
|
||||
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,31 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Used to set required headers on all requests where necessary
|
||||
* @param {any} $q
|
||||
* @param {any} urlHelper
|
||||
*/
|
||||
function requiredHeadersInterceptor($q, urlHelper) {
|
||||
return {
|
||||
//dealing with requests:
|
||||
'request': function (config) {
|
||||
|
||||
// This is a standard header that should be sent for all ajax requests and is required for
|
||||
// how the server handles auth rejections, etc... see https://github.com/dotnet/aspnetcore/blob/a2568cbe1e8dd92d8a7976469100e564362f778e/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L106-L107
|
||||
config.headers["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
// Set the debug header if in debug mode
|
||||
var queryStrings = urlHelper.getQueryStringParams();
|
||||
if (queryStrings.umbDebug === "true" || queryStrings.umbdebug === "true") {
|
||||
config.headers["X-UMB-DEBUG"] = "true";
|
||||
}
|
||||
return config;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.interceptors').factory('requiredHeadersInterceptor', requiredHeadersInterceptor);
|
||||
|
||||
|
||||
})();
|
||||
@@ -8,6 +8,9 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService',
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("X-UMB-XSRF-TOKEN", $cookies["UMB-XSRF-TOKEN"]);
|
||||
// This is a standard header that should be sent for all ajax requests and is required for
|
||||
// how the server handles auth rejections, etc... see https://github.com/dotnet/aspnetcore/blob/a2568cbe1e8dd92d8a7976469100e564362f778e/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L106-L107
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
var queryStrings = urlHelper.getQueryStringParams();
|
||||
if (queryStrings.umbDebug === "true" || queryStrings.umbdebug === "true") {
|
||||
xhr.setRequestHeader("X-UMB-DEBUG", "true");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@using Umbraco.Web.Composing
|
||||
@using Umbraco.Web
|
||||
@using Umbraco.Web.WebAssets
|
||||
@using Umbraco.Web.BackOffice.Security
|
||||
@using Umbraco.Web.Common.Security
|
||||
@using Umbraco.Core.WebAssets
|
||||
@using Umbraco.Core.Configuration
|
||||
@using Umbraco.Core.Hosting
|
||||
|
||||
@@ -157,32 +157,6 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently logged in Umbraco user
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// We have the attribute [SetAngularAntiForgeryTokens] applied because this method is called initially to determine if the user
|
||||
/// is valid before the login screen is displayed. The Auth cookie can be persisted for up to a day but the csrf cookies are only session
|
||||
/// cookies which means that the auth cookie could be valid but the csrf cookies are no longer there, in that case we need to re-set the csrf cookies.
|
||||
/// </remarks>
|
||||
[WebApi.UmbracoAuthorize]
|
||||
[SetAngularAntiForgeryTokens]
|
||||
[CheckIfUserTicketDataIsStale]
|
||||
public UserDetail GetCurrentUser()
|
||||
{
|
||||
var user = Security.CurrentUser;
|
||||
var result = Mapper.Map<UserDetail>(user);
|
||||
var httpContextAttempt = TryGetHttpContext();
|
||||
if (httpContextAttempt.Success)
|
||||
{
|
||||
//set their remaining seconds
|
||||
result.SecondsUntilTimeout = httpContextAttempt.Result.GetRemainingAuthSeconds();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a user is invited they are not approved but we need to resolve the partially logged on (non approved)
|
||||
/// user.
|
||||
@@ -440,20 +414,7 @@ namespace Umbraco.Web.Editors
|
||||
// NOTE: This has been migrated to netcore, but in netcore we don't explicitly set the principal in this method, that's done in ConfigureUmbracoBackOfficeCookieOptions so don't worry about that
|
||||
private HttpResponseMessage SetPrincipalAndReturnUserDetail(IUser user, IPrincipal principal)
|
||||
{
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
if (principal == null) throw new ArgumentNullException(nameof(principal));
|
||||
|
||||
var userDetail = Mapper.Map<UserDetail>(user);
|
||||
// update the userDetail and set their remaining seconds
|
||||
userDetail.SecondsUntilTimeout = TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds;
|
||||
|
||||
// create a response with the userDetail object
|
||||
var response = Request.CreateResponse(HttpStatusCode.OK, userDetail);
|
||||
|
||||
// ensure the user is set for the current request
|
||||
Request.SetPrincipalForRequest(principal);
|
||||
|
||||
return response;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private string ConstructCallbackUrl(int userId, string code)
|
||||
|
||||
@@ -90,20 +90,6 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
protected IAuthenticationManager AuthenticationManager => OwinContext.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// Render the default view
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<ActionResult> Default()
|
||||
{
|
||||
return await RenderDefaultOrProcessExternalLoginAsync(
|
||||
() =>
|
||||
View(GlobalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _hostingEnvironment, _runtimeSettings, _securitySettings)),
|
||||
() =>
|
||||
View(GlobalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _hostingEnvironment, _runtimeSettings, _securitySettings))
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> VerifyInvite(string invite)
|
||||
{
|
||||
@@ -184,28 +170,6 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the JavaScript object representing the static server variables javascript object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[UmbracoAuthorize(Order = 0)]
|
||||
[MinifyJavaScriptResult(Order = 1)]
|
||||
public JavaScriptResult ServerVariables()
|
||||
{
|
||||
var serverVars = new BackOfficeServerVariables(Url, _runtimeState, _features, GlobalSettings, _umbracoVersion, _contentSettings, _treeCollection, _hostingEnvironment, _runtimeSettings, _securitySettings, _runtimeMinifier);
|
||||
|
||||
//cache the result if debugging is disabled
|
||||
var result = _hostingEnvironment.IsDebugMode
|
||||
? ServerVariablesParser.Parse(serverVars.GetServerVariables())
|
||||
: AppCaches.RuntimeCache.GetCacheItem<string>(
|
||||
typeof(BackOfficeController) + "ServerVariables",
|
||||
() => ServerVariablesParser.Parse(serverVars.GetServerVariables()),
|
||||
new TimeSpan(0, 10, 0));
|
||||
|
||||
return JavaScript(result);
|
||||
}
|
||||
|
||||
|
||||
// TODO: for converting to netcore, some examples:
|
||||
// * https://github.com/dotnet/aspnetcore/blob/master/src/Identity/samples/IdentitySample.Mvc/Controllers/AccountController.cs
|
||||
// * https://github.com/dotnet/aspnetcore/blob/master/src/MusicStore/samples/MusicStore/Controllers/AccountController.cs
|
||||
|
||||
@@ -147,19 +147,6 @@ namespace Umbraco.Web.Security
|
||||
//Create the default options and provider
|
||||
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
|
||||
authOptions.Provider = new BackOfficeCookieAuthenticationProvider(userService, runtimeState, globalSettings, hostingEnvironment, securitySettings)
|
||||
{
|
||||
// Enables the application to validate the security stamp when the user
|
||||
// logs in. This is a security feature which is used when you
|
||||
// change a password or add an external login to your account.
|
||||
OnValidateIdentity = UmbracoSecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeSignInManager, BackOfficeOwinUserManager, BackOfficeIdentityUser>(
|
||||
TimeSpan.FromMinutes(30),
|
||||
(signInManager, manager, user) => signInManager.CreateUserIdentityAsync(user),
|
||||
identity => identity.GetUserId()),
|
||||
|
||||
};
|
||||
|
||||
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, hostingEnvironment, requestCache, authOptions, stage);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Umbraco.Web.Security
|
||||
if (ex is FormatException || ex is JsonReaderException)
|
||||
{
|
||||
// this will occur if the cookie data is invalid
|
||||
http.UmbracoLogout();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -86,15 +86,10 @@ namespace Umbraco.Web.Security
|
||||
/// This will return the current back office identity.
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="authenticateRequestIfNotFound">
|
||||
/// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the
|
||||
/// request just as is done in the Umbraco module and then set the current identity if it is valid.
|
||||
/// Just like in the UmbracoModule, if this is true then the user's culture will be assigned to the request.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns the current back office identity if an admin is authenticated otherwise null
|
||||
/// </returns>
|
||||
public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http, bool authenticateRequestIfNotFound)
|
||||
public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException(nameof(http));
|
||||
if (http.User == null) return null; //there's no user at all so no identity
|
||||
@@ -103,59 +98,20 @@ namespace Umbraco.Web.Security
|
||||
var backOfficeIdentity = http.User.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
|
||||
if (authenticateRequestIfNotFound == false) return null;
|
||||
|
||||
// even if authenticateRequestIfNotFound is true we cannot continue if the request is actually authenticated
|
||||
// which would mean something strange is going on that it is not an umbraco identity.
|
||||
if (http.User.Identity.IsAuthenticated) return null;
|
||||
|
||||
// So the user is not authed but we've been asked to do the auth if authenticateRequestIfNotFound = true,
|
||||
// which might occur in old webforms style things or for routes that aren't included as a back office request.
|
||||
// in this case, we are just reverting to authing using the cookie.
|
||||
|
||||
// TODO: Even though this is in theory legacy, we have legacy bits laying around and we'd need to do the auth based on
|
||||
// how the Module will eventually do it (by calling in to any registered authenticators).
|
||||
|
||||
var ticket = http.GetUmbracoAuthTicket();
|
||||
if (http.AuthenticateCurrentRequest(ticket, true))
|
||||
{
|
||||
//now we 'should have an umbraco identity
|
||||
return http.User.Identity as UmbracoBackOfficeIdentity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will return the current back office identity.
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="authenticateRequestIfNotFound">
|
||||
/// If set to true and a back office identity is not found and not authenticated, this will attempt to authenticate the
|
||||
/// request just as is done in the Umbraco module and then set the current identity if it is valid
|
||||
/// </param>
|
||||
/// <param name="http"></param>
|
||||
/// <returns>
|
||||
/// Returns the current back office identity if an admin is authenticated otherwise null
|
||||
/// </returns>
|
||||
internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http, bool authenticateRequestIfNotFound)
|
||||
internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
return new HttpContextWrapper(http).GetCurrentIdentity(authenticateRequestIfNotFound);
|
||||
}
|
||||
|
||||
public static void UmbracoLogout(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
Logout(http, Current.Configs.Security().AuthCookieName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This clears the forms authentication cookie
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
internal static void UmbracoLogout(this HttpContext http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
new HttpContextWrapper(http).UmbracoLogout();
|
||||
return new HttpContextWrapper(http).GetCurrentIdentity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,11 +126,7 @@ namespace Umbraco.Web.Security
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of seconds the user has until their auth session times out
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <returns></returns>
|
||||
// NOTE: Migrated to netcore (though in a different way)
|
||||
public static double GetRemainingAuthSeconds(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException(nameof(http));
|
||||
@@ -182,11 +134,7 @@ namespace Umbraco.Web.Security
|
||||
return ticket.GetRemainingAuthSeconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of seconds the user has until their auth session times out
|
||||
/// </summary>
|
||||
/// <param name="ticket"></param>
|
||||
/// <returns></returns>
|
||||
// NOTE: Migrated to netcore (though in a different way)
|
||||
public static double GetRemainingAuthSeconds(this AuthenticationTicket ticket)
|
||||
{
|
||||
var utcExpired = ticket?.Properties.ExpiresUtc;
|
||||
@@ -218,52 +166,6 @@ namespace Umbraco.Web.Security
|
||||
return GetAuthTicket(ctx, Current.Configs.Security().AuthCookieName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This clears the forms authentication cookie
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="cookieName"></param>
|
||||
private static void Logout(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
// We need to clear the sessionId from the database. This is legacy code to do any logging out and shouldn't really be used at all but in any case
|
||||
// we need to make sure the session is cleared. Due to the legacy nature of this it means we need to use singletons
|
||||
if (http.User != null)
|
||||
{
|
||||
var claimsIdentity = http.User.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity != null)
|
||||
{
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
Guid guidSession;
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
|
||||
{
|
||||
Current.Services.UserService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
// clear the preview cookie and external login
|
||||
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName, Constants.Security.BackOfficeExternalCookieName };
|
||||
foreach (var c in cookies)
|
||||
{
|
||||
// remove from the request
|
||||
http.Request.Cookies.Remove(c);
|
||||
|
||||
// expire from the response
|
||||
var formsCookie = http.Response.Cookies[c];
|
||||
if (formsCookie != null)
|
||||
{
|
||||
// this will expire immediately and be removed from the browser
|
||||
formsCookie.Expires = DateTime.Now.AddYears(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ensure there's def an expired cookie
|
||||
http.Response.Cookies.Add(new HttpCookie(c) { Expires = DateTime.Now.AddYears(-1) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthenticationTicket GetAuthTicket(this IOwinContext owinCtx, string cookieName)
|
||||
{
|
||||
var asDictionary = new Dictionary<string, string>();
|
||||
@@ -313,7 +215,7 @@ namespace Umbraco.Web.Security
|
||||
catch (Exception)
|
||||
{
|
||||
// occurs when decryption fails
|
||||
http.Logout(cookieName);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -334,23 +236,6 @@ namespace Umbraco.Web.Security
|
||||
return secureDataFormat.Unprotect(formsCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the thread culture is set based on the back office user's culture
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
public static void EnsureCulture(this IIdentity identity)
|
||||
{
|
||||
if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated)
|
||||
{
|
||||
Thread.CurrentThread.CurrentUICulture =
|
||||
Thread.CurrentThread.CurrentCulture = UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
// TODO: Migrate this logic to cookie events in ConfigureUmbracoBackOfficeCookieOptions
|
||||
|
||||
public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
@@ -29,97 +32,12 @@ namespace Umbraco.Web.Security
|
||||
_securitySettings = securitySettings;
|
||||
}
|
||||
|
||||
public override void ResponseSignIn(CookieResponseSignInContext context)
|
||||
{
|
||||
if (context.Identity is UmbracoBackOfficeIdentity backOfficeIdentity)
|
||||
{
|
||||
//generate a session id and assign it
|
||||
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
|
||||
|
||||
var session = _runtimeState.Level == RuntimeLevel.Run
|
||||
? _userService.CreateLoginSession(backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
backOfficeIdentity.SessionId = session.ToString();
|
||||
|
||||
//since it is a cookie-based authentication add that claim
|
||||
backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity));
|
||||
}
|
||||
|
||||
base.ResponseSignIn(context);
|
||||
}
|
||||
|
||||
public override void ResponseSignOut(CookieResponseSignOutContext context)
|
||||
{
|
||||
//Clear the user's session on sign out
|
||||
if (context?.OwinContext?.Authentication?.User?.Identity != null)
|
||||
{
|
||||
var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity;
|
||||
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession))
|
||||
{
|
||||
_userService.ClearLoginSession(guidSession);
|
||||
}
|
||||
}
|
||||
|
||||
base.ResponseSignOut(context);
|
||||
|
||||
//Make sure the definitely all of these cookies are cleared when signing out with cookies
|
||||
context.Response.Cookies.Append(SessionIdValidator.CookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
context.Response.Cookies.Append(_securitySettings.AuthCookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
context.Response.Cookies.Append(Constants.Web.PreviewCookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
context.Response.Cookies.Append(Constants.Security.BackOfficeExternalCookieName, "", new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid
|
||||
/// </summary>
|
||||
/// <param name="context"/>
|
||||
/// <returns/>
|
||||
public override async Task ValidateIdentity(CookieValidateIdentityContext context)
|
||||
{
|
||||
//ensure the thread culture is set
|
||||
context?.Identity?.EnsureCulture();
|
||||
|
||||
await EnsureValidSessionId(context);
|
||||
|
||||
if (context?.Identity == null)
|
||||
{
|
||||
context?.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
||||
return;
|
||||
}
|
||||
await base.ValidateIdentity(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the user has a valid session id
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// So that we are not overloading the database this throttles it's check to every minute
|
||||
/// </remarks>
|
||||
protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context)
|
||||
{
|
||||
if (_runtimeState.Level == RuntimeLevel.Run)
|
||||
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _globalSettings, _hostingEnvironment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Umbraco.Web.Security
|
||||
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
|
||||
options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
|
||||
options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role;
|
||||
options.ClaimsIdentity.SecurityStampClaimType = Constants.Web.SecurityStampClaimType;
|
||||
options.ClaimsIdentity.SecurityStampClaimType = Constants.Security.SecurityStampClaimType;
|
||||
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
options.Lockout.MaxFailedAccessAttempts = passwordConfiguration.MaxFailedAccessAttemptsBeforeLockout;
|
||||
|
||||
@@ -110,8 +110,9 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
//We also need to re-validate the user's session if we are relying on this ping to keep their session alive
|
||||
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings);
|
||||
// NOTE: SessionIdValidator has been moved to netcore
|
||||
////We also need to re-validate the user's session if we are relying on this ping to keep their session alive
|
||||
//await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity, _globalSettings);
|
||||
}
|
||||
else if (remainingSeconds <= 30)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
@@ -59,7 +60,10 @@ namespace Umbraco.Web.Security
|
||||
//Ok, we've got a real ticket, now we can add this ticket's identity to the current
|
||||
// Principal, this means we'll have 2 identities assigned to the principal which we can
|
||||
// use to authorize the preview and allow for a back office User.
|
||||
claimsPrincipal.AddIdentity(UmbracoBackOfficeIdentity.FromClaimsIdentity(unprotected.Identity));
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(unprotected.Identity, out var umbracoIdentity))
|
||||
throw new InvalidOperationException("Cannot convert identity");
|
||||
|
||||
claimsPrincipal.AddIdentity(umbracoIdentity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,8 @@ namespace Umbraco.Web.Security
|
||||
return null;
|
||||
}
|
||||
|
||||
UmbracoBackOfficeIdentity identity;
|
||||
|
||||
try
|
||||
{
|
||||
identity = UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//if it cannot be created return null, will be due to serialization errors in user data most likely due to corrupt cookies or cookies
|
||||
//for previous versions of Umbraco
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(decrypt.Identity, out var identity))
|
||||
return null;
|
||||
}
|
||||
|
||||
//return the ticket with a UmbracoBackOfficeIdentity
|
||||
var ticket = new AuthenticationTicket(identity, decrypt.Properties);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Adapted from Microsoft.AspNet.Identity.Owin.SecurityStampValidator
|
||||
/// </summary>
|
||||
public class UmbracoSecurityStampValidator
|
||||
{
|
||||
public static Func<CookieValidateIdentityContext, Task> OnValidateIdentity<TSignInManager, TManager, TUser>(
|
||||
TimeSpan validateInterval,
|
||||
Func<BackOfficeSignInManager, TManager, TUser, Task<ClaimsIdentity>> regenerateIdentityCallback,
|
||||
Func<ClaimsIdentity, string> getUserIdCallback)
|
||||
where TSignInManager : BackOfficeSignInManager
|
||||
where TManager : BackOfficeUserManager<TUser>
|
||||
where TUser : BackOfficeIdentityUser
|
||||
{
|
||||
if (getUserIdCallback == null) throw new ArgumentNullException(nameof(getUserIdCallback));
|
||||
|
||||
return async context =>
|
||||
{
|
||||
var currentUtc = context.Options?.SystemClock?.UtcNow ?? DateTimeOffset.UtcNow;
|
||||
|
||||
var issuedUtc = context.Properties.IssuedUtc;
|
||||
|
||||
// Only validate if enough time has elapsed
|
||||
var validate = issuedUtc == null;
|
||||
if (issuedUtc != null)
|
||||
{
|
||||
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||
validate = timeElapsed > validateInterval;
|
||||
}
|
||||
|
||||
if (validate)
|
||||
{
|
||||
var manager = context.OwinContext.Get<TManager>();
|
||||
if (manager == null) throw new InvalidOperationException("Unable to load BackOfficeUserManager");
|
||||
|
||||
var signInManager = context.OwinContext.Get<TSignInManager>();
|
||||
if (signInManager == null) throw new InvalidOperationException("Unable to load BackOfficeSignInManager");
|
||||
|
||||
var userId = getUserIdCallback(context.Identity);
|
||||
|
||||
if (userId != null)
|
||||
{
|
||||
var user = await manager.FindByIdAsync(userId);
|
||||
var reject = true;
|
||||
|
||||
// Refresh the identity if the stamp matches, otherwise reject
|
||||
if (user != null && manager.SupportsUserSecurityStamp)
|
||||
{
|
||||
var securityStamp = context.Identity.FindFirstValue(Constants.Web.SecurityStampClaimType);
|
||||
var newSecurityStamp = await manager.GetSecurityStampAsync(user);
|
||||
|
||||
if (securityStamp == newSecurityStamp)
|
||||
{
|
||||
reject = false;
|
||||
// Regenerate fresh claims if possible and resign in
|
||||
if (regenerateIdentityCallback != null)
|
||||
{
|
||||
var identity = await regenerateIdentityCallback.Invoke(signInManager, manager, user);
|
||||
if (identity != null)
|
||||
{
|
||||
// Fix for regression where this value is not updated
|
||||
// Setting it to null so that it is refreshed by the cookie middleware
|
||||
context.Properties.IssuedUtc = null;
|
||||
context.Properties.ExpiresUtc = null;
|
||||
context.OwinContext.Authentication.SignIn(context.Properties, identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reject)
|
||||
{
|
||||
context.RejectIdentity();
|
||||
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,186 +13,39 @@ using Umbraco.Core.Models;
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A utility class used for dealing with USER security in Umbraco
|
||||
/// </summary>
|
||||
// NOTE: Moved to netcore
|
||||
public class WebSecurity : IWebSecurity
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
public IUser CurrentUser => throw new NotImplementedException();
|
||||
|
||||
public WebSecurity(IHttpContextAccessor httpContextAccessor, IUserService userService, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_userService = userService;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
private IUser _currentUser;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current user.
|
||||
/// </summary>
|
||||
/// <value>The current user.</value>
|
||||
public IUser CurrentUser
|
||||
{
|
||||
get
|
||||
{
|
||||
//only load it once per instance! (but make sure groups are loaded)
|
||||
if (_currentUser == null)
|
||||
{
|
||||
var id = GetUserId();
|
||||
_currentUser = id ? _userService.GetUserById(id.Result) : null;
|
||||
}
|
||||
|
||||
return _currentUser;
|
||||
}
|
||||
}
|
||||
|
||||
private BackOfficeSignInManager _signInManager;
|
||||
private BackOfficeSignInManager SignInManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_signInManager == null)
|
||||
{
|
||||
var mgr = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().Get<BackOfficeSignInManager>();
|
||||
if (mgr == null)
|
||||
{
|
||||
throw new NullReferenceException("Could not resolve an instance of " + typeof(BackOfficeSignInManager) + " from the " + typeof(IOwinContext));
|
||||
}
|
||||
_signInManager = mgr;
|
||||
}
|
||||
return _signInManager;
|
||||
}
|
||||
}
|
||||
|
||||
private BackOfficeOwinUserManager _userManager;
|
||||
protected BackOfficeOwinUserManager UserManager
|
||||
=> _userManager ?? (_userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager());
|
||||
|
||||
[Obsolete("This needs to be removed, ASP.NET Identity should always be used for this operation, this is currently only used in the installer which needs to be updated")]
|
||||
public double PerformLogin(int userId)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
|
||||
var owinCtx = httpContext.GetOwinContext();
|
||||
//ensure it's done for owin too
|
||||
owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
var user = UserManager.FindByIdAsync(userId.ToString()).Result;
|
||||
|
||||
SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait();
|
||||
|
||||
httpContext.SetPrincipalForRequest(owinCtx.Request.User);
|
||||
|
||||
return TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes).TotalSeconds;
|
||||
}
|
||||
|
||||
[Obsolete("This needs to be removed, ASP.NET Identity should always be used for this operation, this is currently only used in the installer which needs to be updated")]
|
||||
public void ClearCurrentLogin()
|
||||
{
|
||||
var httpContext = _httpContextAccessor.GetRequiredHttpContext();
|
||||
httpContext.UmbracoLogout();
|
||||
httpContext.GetOwinContext().Authentication.SignOut(
|
||||
Core.Constants.Security.BackOfficeAuthenticationType,
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current user's id.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Attempt<int> GetUserId()
|
||||
{
|
||||
var identity = _httpContextAccessor.GetRequiredHttpContext().GetCurrentIdentity(false);
|
||||
return identity == null ? Attempt.Fail<int>() : Attempt.Succeed(Convert.ToInt32(identity.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the currently logged in user and ensures they are not timed out
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool ValidateCurrentUser()
|
||||
{
|
||||
return ValidateCurrentUser(false, true) == ValidateRequestAttempt.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the current user assigned to the request and ensures the stored user data is valid
|
||||
/// </summary>
|
||||
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
|
||||
/// <param name="requiresApproval">If true requires that the user is approved to be validated</param>
|
||||
/// <returns></returns>
|
||||
public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true)
|
||||
{
|
||||
//This will first check if the current user is already authenticated - which should be the case in nearly all circumstances
|
||||
// since the authentication happens in the Module, that authentication also checks the ticket expiry. We don't
|
||||
// need to check it a second time because that requires another decryption phase and nothing can tamper with it during the request.
|
||||
|
||||
if (IsAuthenticated() == false)
|
||||
{
|
||||
//There is no user
|
||||
if (throwExceptions) throw new InvalidOperationException("The user has no umbraco contextid - try logging in");
|
||||
return ValidateRequestAttempt.FailedNoContextId;
|
||||
}
|
||||
|
||||
var user = CurrentUser;
|
||||
|
||||
// Check for console access
|
||||
if (user == null || (requiresApproval && user.IsApproved == false) || (user.IsLockedOut && RequestIsInUmbracoApplication(_httpContextAccessor, _globalSettings, _hostingEnvironment)))
|
||||
{
|
||||
if (throwExceptions) throw new ArgumentException("You have no privileges to the umbraco console. Please contact your administrator");
|
||||
return ValidateRequestAttempt.FailedNoPrivileges;
|
||||
}
|
||||
return ValidateRequestAttempt.Success;
|
||||
|
||||
}
|
||||
|
||||
private static bool RequestIsInUmbracoApplication(IHttpContextAccessor httpContextAccessor, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
return httpContextAccessor.GetRequiredHttpContext().Request.Path.ToLower().IndexOf(hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath).ToLower(), StringComparison.Ordinal) > -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorizes the full request, checks for SSL and validates the current user
|
||||
/// </summary>
|
||||
/// <param name="throwExceptions">set to true if you want exceptions to be thrown if failed</param>
|
||||
/// <returns></returns>
|
||||
public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false)
|
||||
{
|
||||
// check for secure connection
|
||||
if (_globalSettings.UseHttps && _httpContextAccessor.GetRequiredHttpContext().Request.IsSecureConnection == false)
|
||||
{
|
||||
if (throwExceptions) throw new SecurityException("This installation requires a secure connection (via SSL). Please update the URL to include https://");
|
||||
return ValidateRequestAttempt.FailedNoSsl;
|
||||
}
|
||||
return ValidateCurrentUser(throwExceptions);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified user as access to the app
|
||||
/// </summary>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
public bool UserHasSectionAccess(string section, IUser user)
|
||||
public Attempt<int> GetUserId()
|
||||
{
|
||||
return user.HasSectionAccess(section);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a back office user is logged in
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsAuthenticated()
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated && httpContext.GetCurrentIdentity(false) != null;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool UserHasSectionAccess(string section, IUser user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool ValidateCurrentUser()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
<Compile Include="Macros\PartialViewMacroEngine.cs" />
|
||||
<Compile Include="Mvc\UmbracoViewPageOfTModel.cs" />
|
||||
<Compile Include="Security\IdentityFactoryMiddleware.cs" />
|
||||
<Compile Include="Security\WebSecurity.cs" />
|
||||
<Compile Include="WebApi\Filters\UmbracoTreeAuthorizeAttribute.cs" />
|
||||
<Compile Include="WebAssets\CDF\ClientDependencyRuntimeMinifier.cs" />
|
||||
<Compile Include="Models\NoNodesViewModel.cs" />
|
||||
@@ -197,7 +198,6 @@
|
||||
<Compile Include="Security\OwinDataProtectorTokenProvider.cs" />
|
||||
<Compile Include="Security\PublicAccessChecker.cs" />
|
||||
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
|
||||
<Compile Include="Security\UmbracoSecurityStampValidator.cs" />
|
||||
<Compile Include="Security\UserAwarePasswordHasher.cs" />
|
||||
<Compile Include="StringExtensions.cs" />
|
||||
<Compile Include="Trees\ITreeNodeController.cs" />
|
||||
@@ -221,7 +221,6 @@
|
||||
<Compile Include="Mvc\ContainerControllerFactory.cs" />
|
||||
<Compile Include="OwinExtensions.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />
|
||||
<Compile Include="Security\SessionIdValidator.cs" />
|
||||
<Compile Include="SignalR\PreviewHubComposer.cs" />
|
||||
<Compile Include="Trees\FilesTreeController.cs" />
|
||||
<Compile Include="WebAssets\CDF\ClientDependencyConfiguration.cs" />
|
||||
@@ -394,7 +393,6 @@
|
||||
<Compile Include="Mvc\ControllerFactoryExtensions.cs" />
|
||||
<Compile Include="Mvc\IRenderMvcController.cs" />
|
||||
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
|
||||
<Compile Include="Security\WebSecurity.cs" />
|
||||
<Compile Include="CdfLogger.cs" />
|
||||
<Compile Include="Controllers\UmbLoginController.cs" />
|
||||
<Compile Include="UrlHelperExtensions.cs" />
|
||||
|
||||
@@ -67,9 +67,7 @@ namespace Umbraco.Web
|
||||
_variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture);
|
||||
}
|
||||
|
||||
var webSecurity = new WebSecurity(_httpContextAccessor, _userService, _globalSettings, _hostingEnvironment);
|
||||
|
||||
return new UmbracoContext(_httpContextAccessor, _publishedSnapshotService, webSecurity, _globalSettings, _hostingEnvironment, _variationContextAccessor, _uriUtility, _cookieManager);
|
||||
return new UmbracoContext(_httpContextAccessor, _publishedSnapshotService, new WebSecurity(), _globalSettings, _hostingEnvironment, _variationContextAccessor, _uriUtility, _cookieManager);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
Reference in New Issue
Block a user