Merge pull request #9833 from umbraco/9622-remove-backofficeidentity
Netcore: Remove UmbracoBackOfficeIdentity
This commit is contained in:
@@ -2,8 +2,11 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -72,5 +75,271 @@ namespace Umbraco.Extensions
|
||||
|
||||
return identity.FindFirst(claimType)?.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the required claim types for a back office identity
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not include the role claim type or allowed apps type since that is a collection and in theory could be empty
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> RequiredBackOfficeClaimTypes => new[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier, //id
|
||||
ClaimTypes.Name, //username
|
||||
ClaimTypes.GivenName,
|
||||
// Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null...
|
||||
// Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SecurityStampClaimType
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Verify that a ClaimsIdentity has all the required claim types
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <param name="verifiedIdentity">Verified identity wrapped in a ClaimsIdentity with BackOfficeAuthentication type</param>
|
||||
/// <returns>True if ClaimsIdentity</returns>
|
||||
public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, out ClaimsIdentity verifiedIdentity)
|
||||
{
|
||||
// Validate that all required claims exist
|
||||
foreach (var claimType in RequiredBackOfficeClaimTypes)
|
||||
{
|
||||
if (identity.HasClaim(x => x.Type == claimType) == false ||
|
||||
identity.HasClaim(x => x.Type == claimType && x.Value.IsNullOrWhiteSpace()))
|
||||
{
|
||||
verifiedIdentity = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
verifiedIdentity = new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the required claims to be a BackOffice ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity">this</param>
|
||||
/// <param name="userId">The users Id</param>
|
||||
/// <param name="username">Username</param>
|
||||
/// <param name="realName">Real name</param>
|
||||
/// <param name="startContentNodes">Start content nodes</param>
|
||||
/// <param name="startMediaNodes">Start media nodes</param>
|
||||
/// <param name="culture">The locality of the user</param>
|
||||
/// <param name="securityStamp">Security stamp</param>
|
||||
/// <param name="allowedApps">Allowed apps</param>
|
||||
/// <param name="roles">Roles</param>
|
||||
public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username,
|
||||
string realName, IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
string securityStamp, IEnumerable<string> allowedApps, IEnumerable<string> roles)
|
||||
{
|
||||
//This is the id that 'identity' uses to check for the user id
|
||||
if (identity.HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
ClaimTypes.NameIdentifier,
|
||||
userId,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
|
||||
if (identity.HasClaim(x => x.Type == ClaimTypes.Name) == false)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
ClaimTypes.Name,
|
||||
username,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
|
||||
if (identity.HasClaim(x => x.Type == ClaimTypes.GivenName) == false)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
ClaimTypes.GivenName,
|
||||
realName,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
|
||||
if (identity.HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false &&
|
||||
startContentNodes != null)
|
||||
{
|
||||
foreach (var startContentNode in startContentNodes)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
startContentNode.ToInvariantString(),
|
||||
ClaimValueTypes.Integer32,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
}
|
||||
|
||||
if (identity.HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false &&
|
||||
startMediaNodes != null)
|
||||
{
|
||||
foreach (var startMediaNode in startMediaNodes)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
startMediaNode.ToInvariantString(),
|
||||
ClaimValueTypes.Integer32,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
}
|
||||
|
||||
if (identity.HasClaim(x => x.Type == ClaimTypes.Locality) == false)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
ClaimTypes.Locality,
|
||||
culture,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
|
||||
// The security stamp claim is also required
|
||||
if (identity.HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
Constants.Security.SecurityStampClaimType,
|
||||
securityStamp,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
|
||||
// Add each app as a separate claim
|
||||
if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false &&
|
||||
allowedApps != null)
|
||||
{
|
||||
foreach (var application in allowedApps)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
Constants.Security.AllowedApplicationsClaimType,
|
||||
application,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
}
|
||||
|
||||
// Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might
|
||||
// not be made with that factory if it was created with a different ticket so perform the check
|
||||
if (identity.HasClaim(x => x.Type == ClaimsIdentity.DefaultRoleClaimType) == false && roles != null)
|
||||
{
|
||||
// Manually add them
|
||||
foreach (var roleName in roles)
|
||||
{
|
||||
identity.AddClaim(new Claim(
|
||||
identity.RoleClaimType,
|
||||
roleName,
|
||||
ClaimValueTypes.String,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
identity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the start content nodes from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Array of start content nodes</returns>
|
||||
public static int[] GetStartContentNodes(this ClaimsIdentity identity) =>
|
||||
identity.FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType)
|
||||
.Select(node => int.TryParse(node.Value, out var i) ? i : default)
|
||||
.Where(x => x != default).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Get the start media nodes from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Array of start media nodes</returns>
|
||||
public static int[] GetStartMediaNodes(this ClaimsIdentity identity) =>
|
||||
identity.FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType)
|
||||
.Select(node => int.TryParse(node.Value, out var i) ? i : default)
|
||||
.Where(x => x != default).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Get the allowed applications from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns></returns>
|
||||
public static string[] GetAllowedApplications(this ClaimsIdentity identity) => identity
|
||||
.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Get the user ID from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>User ID as integer</returns>
|
||||
public static int GetId(this ClaimsIdentity identity) => int.Parse(identity.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
|
||||
/// <summary>
|
||||
/// Get the real name belonging to the user from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Real name of the user</returns>
|
||||
public static string GetRealName(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
/// <summary>
|
||||
/// Get the username of the user from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Username of the user</returns>
|
||||
public static string GetUsername(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Get the culture string from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Culture string</returns>
|
||||
public static string GetCultureString(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Locality);
|
||||
|
||||
/// <summary>
|
||||
/// Get the security stamp from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Security stamp</returns>
|
||||
public static string GetSecurityStamp(this ClaimsIdentity identity) => identity.FindFirstValue(Constants.Security.SecurityStampClaimType);
|
||||
|
||||
/// <summary>
|
||||
/// Get the roles assigned to a user from a ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
/// <returns>Array of roles</returns>
|
||||
public static string[] GetRoles(this ClaimsIdentity identity) => identity
|
||||
.FindAll(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(role => role.Value).ToArray();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates and existing claim.
|
||||
/// </summary>
|
||||
public static void AddOrUpdateClaim(this ClaimsIdentity identity, Claim claim)
|
||||
{
|
||||
if (identity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(identity));
|
||||
}
|
||||
|
||||
Claim existingClaim = identity.Claims.FirstOrDefault(x => x.Type == claim.Type);
|
||||
identity.TryRemoveClaim(existingClaim);
|
||||
|
||||
identity.AddClaim(claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -24,9 +24,9 @@ namespace Umbraco.Extensions
|
||||
|
||||
public static CultureInfo GetCulture(this IIdentity identity)
|
||||
{
|
||||
if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated)
|
||||
if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) && umbIdentity.IsAuthenticated)
|
||||
{
|
||||
return CultureInfo.GetCultureInfo(umbIdentity.Culture);
|
||||
return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString());
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -14,29 +14,21 @@ namespace Umbraco.Extensions
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This will return the current back office identity if the IPrincipal is the correct type
|
||||
/// This will return the current back office identity if the IPrincipal is the correct type and authenticated.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
public static UmbracoBackOfficeIdentity GetUmbracoIdentity(this IPrincipal user)
|
||||
public static ClaimsIdentity GetUmbracoIdentity(this IPrincipal user)
|
||||
{
|
||||
// TODO: It would be nice to get rid of this and only rely on Claims, not a strongly typed identity instance
|
||||
|
||||
//If it's already a UmbracoBackOfficeIdentity
|
||||
if (user.Identity is UmbracoBackOfficeIdentity backOfficeIdentity) return backOfficeIdentity;
|
||||
|
||||
//Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that
|
||||
if (user is ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
backOfficeIdentity = claimsPrincipal.Identities.OfType<UmbracoBackOfficeIdentity>().FirstOrDefault();
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
}
|
||||
|
||||
//Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd
|
||||
// Check if the identity is a ClaimsIdentity, and that's it's authenticated and has all required claims.
|
||||
if (user.Identity is ClaimsIdentity claimsIdentity
|
||||
&& claimsIdentity.IsAuthenticated
|
||||
&& UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var umbracoIdentity))
|
||||
&& claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity umbracoIdentity))
|
||||
{
|
||||
if (claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType)
|
||||
{
|
||||
return claimsIdentity;
|
||||
}
|
||||
return umbracoIdentity;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom user identity for the Umbraco backoffice
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UmbracoBackOfficeIdentity : ClaimsIdentity
|
||||
{
|
||||
// TODO: Ideally we remove this class and only deal with ClaimsIdentity as a best practice. All things relevant to our own
|
||||
// identity are part of claims. This class would essentially become extension methods on a ClaimsIdentity for resolving
|
||||
// values from it.
|
||||
public static bool FromClaimsIdentity(ClaimsIdentity identity, out UmbracoBackOfficeIdentity backOfficeIdentity)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new UmbracoBackOfficeIdentity
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="realName"></param>
|
||||
/// <param name="startContentNodes"></param>
|
||||
/// <param name="startMediaNodes"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="securityStamp"></param>
|
||||
/// <param name="allowedApps"></param>
|
||||
/// <param name="roles"></param>
|
||||
public UmbracoBackOfficeIdentity(string userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
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(securityStamp))
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp));
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new UmbracoBackOfficeIdentity
|
||||
/// </summary>
|
||||
/// <param name="childIdentity">
|
||||
/// The original identity created by the ClaimsIdentityFactory
|
||||
/// </param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="realName"></param>
|
||||
/// <param name="startContentNodes"></param>
|
||||
/// <param name="startMediaNodes"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="securityStamp"></param>
|
||||
/// <param name="allowedApps"></param>
|
||||
/// <param name="roles"></param>
|
||||
public UmbracoBackOfficeIdentity(ClaimsIdentity childIdentity,
|
||||
string userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
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(securityStamp))
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp));
|
||||
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
public const string Issuer = Constants.Security.BackOfficeAuthenticationType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the required claim types for a back office identity
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not include the role claim type or allowed apps type since that is a collection and in theory could be empty
|
||||
/// </remarks>
|
||||
public static IEnumerable<string> RequiredBackOfficeIdentityClaimTypes => new[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier, //id
|
||||
ClaimTypes.Name, //username
|
||||
ClaimTypes.GivenName,
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SecurityStampClaimType
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Adds claims based on the ctor data
|
||||
/// </summary>
|
||||
private void AddRequiredClaims(string userId, string username, string realName,
|
||||
IEnumerable<int> startContentNodes, IEnumerable<int> startMediaNodes, string culture,
|
||||
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)
|
||||
AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == ClaimTypes.Name) == false)
|
||||
AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == ClaimTypes.GivenName) == false)
|
||||
AddClaim(new Claim(ClaimTypes.GivenName, realName, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && startContentNodes != null)
|
||||
{
|
||||
foreach (var startContentNode in startContentNodes)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, startContentNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && startMediaNodes != null)
|
||||
{
|
||||
foreach (var startMediaNode in startMediaNodes)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, startMediaNode.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
if (HasClaim(x => x.Type == ClaimTypes.Locality) == false)
|
||||
AddClaim(new Claim(ClaimTypes.Locality, culture, 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)
|
||||
{
|
||||
foreach (var application in allowedApps)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.AllowedApplicationsClaimType, application, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
//Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might
|
||||
// not be made with that factory if it was created with a different ticket so perform the check
|
||||
if (HasClaim(x => x.Type == DefaultRoleClaimType) == false && roles != null)
|
||||
{
|
||||
//manually add them
|
||||
foreach (var roleName in roles)
|
||||
{
|
||||
AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Gets the type of authenticated identity.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The type of authenticated identity. This property always returns "UmbracoBackOffice".
|
||||
/// </returns>
|
||||
public override string AuthenticationType => Issuer;
|
||||
|
||||
private int[] _startContentNodes;
|
||||
public int[] StartContentNodes => _startContentNodes ?? (_startContentNodes = FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray());
|
||||
|
||||
private int[] _startMediaNodes;
|
||||
public int[] StartMediaNodes => _startMediaNodes ?? (_startMediaNodes = FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType).Select(app => int.TryParse(app.Value, out var i) ? i : default).Where(x => x != default).ToArray());
|
||||
|
||||
private string[] _allowedApplications;
|
||||
public string[] AllowedApplications => _allowedApplications ?? (_allowedApplications = FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray());
|
||||
|
||||
public int Id => int.Parse(this.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
|
||||
public string RealName => this.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
public string Username => this.FindFirstValue(ClaimTypes.Name);
|
||||
|
||||
public string Culture => this.FindFirstValue(ClaimTypes.Locality);
|
||||
|
||||
public string SecurityStamp => this.FindFirstValue(Constants.Security.SecurityStampClaimType);
|
||||
|
||||
public string[] Roles => FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to remove any temporary claims that shouldn't be copied
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override ClaimsIdentity Clone()
|
||||
{
|
||||
var clone = base.Clone();
|
||||
|
||||
foreach (var claim in clone.FindAll(x => x.Type == Constants.Security.TicketExpiresClaimType).ToList())
|
||||
clone.RemoveClaim(claim);
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,22 @@ namespace Umbraco.Core.Compose
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, IOptions<GlobalSettings> globalSettings)
|
||||
public AuditEventsComponent(
|
||||
IAuditService auditService,
|
||||
IUserService userService,
|
||||
IEntityService entityService,
|
||||
IIpResolver ipResolver,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_auditService = auditService;
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
_ipResolver = ipResolver;
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_globalSettings = globalSettings.Value;
|
||||
}
|
||||
|
||||
@@ -73,7 +81,7 @@ namespace Umbraco.Core.Compose
|
||||
{
|
||||
get
|
||||
{
|
||||
var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity();
|
||||
var identity = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
|
||||
var user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id));
|
||||
return user ?? UnknownUser(_globalSettings);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Security
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Returns a custom <see cref="UmbracoBackOfficeIdentity"/> and allows flowing claims from the external identity
|
||||
/// Returns a ClaimsIdentity that has the required claims, and allows flowing of claims from external identity
|
||||
/// </remarks>
|
||||
public override async Task<ClaimsPrincipal> CreateAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
@@ -43,10 +43,7 @@ namespace Umbraco.Core.Security
|
||||
baseIdentity.AddClaim(new Claim(claim.ClaimType, claim.ClaimValue));
|
||||
}
|
||||
|
||||
// TODO: We want to remove UmbracoBackOfficeIdentity and only rely on ClaimsIdentity, once
|
||||
// that is done then we'll create a ClaimsIdentity with all of the requirements here instead
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(
|
||||
baseIdentity,
|
||||
baseIdentity.AddRequiredClaims(
|
||||
user.Id,
|
||||
user.UserName,
|
||||
user.Name,
|
||||
@@ -57,7 +54,7 @@ namespace Umbraco.Core.Security
|
||||
user.AllowedSections,
|
||||
user.Roles.Select(x => x.RoleId).ToArray());
|
||||
|
||||
return new ClaimsPrincipal(umbracoIdentity);
|
||||
return new ClaimsPrincipal(baseIdentity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -39,24 +39,24 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out UmbracoBackOfficeIdentity backofficeIdentity))
|
||||
if (!claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity verifiedIdentity))
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
Assert.IsNull(backofficeIdentity.Actor);
|
||||
Assert.AreEqual(1234, backofficeIdentity.Id);
|
||||
Assert.IsNull(verifiedIdentity.Actor);
|
||||
Assert.AreEqual(1234, verifiedIdentity.GetId());
|
||||
//// Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
|
||||
Assert.AreEqual("testing", backofficeIdentity.Username);
|
||||
Assert.AreEqual("hello world", backofficeIdentity.RealName);
|
||||
Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length);
|
||||
Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new[] { 5543, 5555 }));
|
||||
Assert.IsTrue(new[] { "content", "media" }.SequenceEqual(backofficeIdentity.AllowedApplications));
|
||||
Assert.AreEqual("en-us", backofficeIdentity.Culture);
|
||||
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
|
||||
Assert.AreEqual(securityStamp, verifiedIdentity.GetSecurityStamp());
|
||||
Assert.AreEqual("testing", verifiedIdentity.GetUsername());
|
||||
Assert.AreEqual("hello world", verifiedIdentity.GetRealName());
|
||||
Assert.AreEqual(1, verifiedIdentity.GetStartContentNodes().Length);
|
||||
Assert.IsTrue(verifiedIdentity.GetStartMediaNodes().UnsortedSequenceEqual(new[] { 5543, 5555 }));
|
||||
Assert.IsTrue(new[] { "content", "media" }.SequenceEqual(verifiedIdentity.GetAllowedApplications()));
|
||||
Assert.AreEqual("en-us", verifiedIdentity.GetCultureString());
|
||||
Assert.IsTrue(new[] { "admin" }.SequenceEqual(verifiedIdentity.GetRoles()));
|
||||
|
||||
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
|
||||
Assert.AreEqual(11, verifiedIdentity.Claims.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -68,7 +68,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _))
|
||||
if (claimsIdentity.VerifyBackOfficeIdentity(out _))
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _))
|
||||
if (claimsIdentity.VerifyBackOfficeIdentity(out _))
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
@@ -112,8 +112,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim("TestClaim1", "test", ClaimValueTypes.Integer32, TestIssuer, TestIssuer)
|
||||
});
|
||||
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
claimsIdentity,
|
||||
claimsIdentity.AddRequiredClaims(
|
||||
"1234",
|
||||
"testing",
|
||||
"hello world",
|
||||
@@ -124,25 +123,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new[] { "content", "media" },
|
||||
new[] { "admin" });
|
||||
|
||||
Assert.AreEqual(12, identity.Claims.Count());
|
||||
Assert.IsNull(identity.Actor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clone()
|
||||
{
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
"1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" });
|
||||
|
||||
// this will be filtered out during cloning
|
||||
identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test"));
|
||||
|
||||
ClaimsIdentity cloned = identity.Clone();
|
||||
Assert.IsNull(cloned.Actor);
|
||||
|
||||
Assert.AreEqual(10, cloned.Claims.Count());
|
||||
Assert.AreEqual(12, claimsIdentity.Claims.Count());
|
||||
Assert.IsNull(claimsIdentity.Actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions
|
||||
{
|
||||
@@ -17,7 +16,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions
|
||||
[Test]
|
||||
public void Get_Remaining_Ticket_Seconds()
|
||||
{
|
||||
var backOfficeIdentity = new UmbracoBackOfficeIdentity(
|
||||
var backOfficeIdentity = new ClaimsIdentity();
|
||||
backOfficeIdentity.AddRequiredClaims(
|
||||
Constants.Security.SuperUserIdAsString,
|
||||
"test",
|
||||
"test",
|
||||
@@ -27,6 +27,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions
|
||||
Guid.NewGuid().ToString(),
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<string>());
|
||||
|
||||
var principal = new ClaimsPrincipal(backOfficeIdentity);
|
||||
|
||||
var expireSeconds = 99;
|
||||
@@ -37,16 +38,52 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions
|
||||
var expires = now.AddSeconds(expireSeconds).ToString("o");
|
||||
|
||||
backOfficeIdentity.AddClaim(new Claim(
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
expires,
|
||||
ClaimValueTypes.DateTime,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
backOfficeIdentity));
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
expires,
|
||||
ClaimValueTypes.DateTime,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
backOfficeIdentity));
|
||||
|
||||
var ticketRemainingSeconds = principal.GetRemainingAuthSeconds(then);
|
||||
|
||||
Assert.AreEqual(remainingSeconds, ticketRemainingSeconds);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddOrUpdateClaim__Should_ensure_a_claim_is_not_added_twice()
|
||||
{
|
||||
var backOfficeIdentity = new ClaimsIdentity();
|
||||
backOfficeIdentity.AddRequiredClaims(
|
||||
Constants.Security.SuperUserIdAsString,
|
||||
"test",
|
||||
"test",
|
||||
Enumerable.Empty<int>(),
|
||||
Enumerable.Empty<int>(),
|
||||
"en-US",
|
||||
Guid.NewGuid().ToString(),
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<string>());
|
||||
|
||||
var expireSeconds = 99;
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.Now;
|
||||
|
||||
var expires = now.AddSeconds(expireSeconds).ToString("o");
|
||||
|
||||
|
||||
var claim = new Claim(
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
expires,
|
||||
ClaimValueTypes.DateTime,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
backOfficeIdentity);
|
||||
|
||||
backOfficeIdentity.AddOrUpdateClaim(claim);
|
||||
backOfficeIdentity.AddOrUpdateClaim(claim);
|
||||
|
||||
Assert.AreEqual(1, backOfficeIdentity.Claims.Count(x=>x.Type == Constants.Security.TicketExpiresClaimType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateAsync_Should_Create_Principal_With_Umbraco_Identity()
|
||||
public async Task CreateAsync_Should_Create_Principal_With_Claims_Identity()
|
||||
{
|
||||
BackOfficeClaimsPrincipalFactory sut = CreateSut();
|
||||
|
||||
ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
var umbracoBackOfficeIdentity = claimsPrincipal.Identity as UmbracoBackOfficeIdentity;
|
||||
var umbracoBackOfficeIdentity = claimsPrincipal.Identity as ClaimsIdentity;
|
||||
Assert.IsNotNull(umbracoBackOfficeIdentity);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Web.BackOffice.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
@@ -24,18 +22,21 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
private HttpContext GetHttpContext()
|
||||
{
|
||||
var identity = new ClaimsIdentity();
|
||||
identity.AddRequiredClaims(
|
||||
Constants.Security.SuperUserIdAsString,
|
||||
"test",
|
||||
"test",
|
||||
Enumerable.Empty<int>(),
|
||||
Enumerable.Empty<int>(),
|
||||
"en-US",
|
||||
Guid.NewGuid().ToString(),
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<string>());
|
||||
|
||||
var httpContext = new DefaultHttpContext()
|
||||
{
|
||||
User = new ClaimsPrincipal(new UmbracoBackOfficeIdentity(
|
||||
Constants.Security.SuperUserIdAsString,
|
||||
"test",
|
||||
"test",
|
||||
Enumerable.Empty<int>(),
|
||||
Enumerable.Empty<int>(),
|
||||
"en-US",
|
||||
Guid.NewGuid().ToString(),
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<string>()))
|
||||
User = new ClaimsPrincipal(identity)
|
||||
};
|
||||
httpContext.Request.IsHttps = true;
|
||||
return httpContext;
|
||||
|
||||
@@ -584,7 +584,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(3, result.Length);
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1173, 1046 }));
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1174, 1173, 1046 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -597,7 +597,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1046 }));
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1046 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -710,7 +710,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(10, result.Count());
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 }));
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -723,7 +723,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(9, result.Count());
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 }));
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Infrastructure;
|
||||
using Owin;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
@@ -30,8 +29,18 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
|
||||
{
|
||||
var securityStamp = Guid.NewGuid().ToString();
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
Constants.Security.SuperUserIdAsString, "admin", "Admin", new[] { -1 }, new[] { -1 }, "en-US", securityStamp, new[] { "content", "media", "members" }, new[] { "admin" });
|
||||
|
||||
var identity = new ClaimsIdentity();
|
||||
identity.AddRequiredClaims(
|
||||
Cms.Core.Constants.Security.SuperUserIdAsString,
|
||||
"admin",
|
||||
"Admin",
|
||||
new[] { -1 },
|
||||
new[] { -1 },
|
||||
"en-US",
|
||||
securityStamp,
|
||||
new[] { "content", "media", "members" },
|
||||
new[] { "admin" });
|
||||
|
||||
return Task.FromResult(new AuthenticationTicket(
|
||||
identity,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
@@ -22,6 +23,7 @@ using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
@@ -97,29 +99,29 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
//chuck it into the props since this is what MS does when hosted and it's needed there
|
||||
request.Properties["MS_HttpContext"] = httpContext;
|
||||
|
||||
var backofficeIdentity = (UmbracoBackOfficeIdentity) owinContext.Authentication.User.Identity;
|
||||
var backofficeIdentity = (ClaimsIdentity) owinContext.Authentication.User.Identity;
|
||||
|
||||
var backofficeSecurity = new Mock<IBackOfficeSecurity>();
|
||||
|
||||
//mock CurrentUser
|
||||
var groups = new List<ReadOnlyUserGroup>();
|
||||
for (var index = 0; index < backofficeIdentity.Roles.Length; index++)
|
||||
for (var index = 0; index < backofficeIdentity.GetRoles().Length; index++)
|
||||
{
|
||||
var role = backofficeIdentity.Roles[index];
|
||||
var role = backofficeIdentity.GetRoles()[index];
|
||||
groups.Add(new ReadOnlyUserGroup(index + 1, role, "icon-user", null, null, role, new string[0], new string[0]));
|
||||
}
|
||||
var mockUser = MockedUser.GetUserMock();
|
||||
mockUser.Setup(x => x.IsApproved).Returns(true);
|
||||
mockUser.Setup(x => x.IsLockedOut).Returns(false);
|
||||
mockUser.Setup(x => x.AllowedSections).Returns(backofficeIdentity.AllowedApplications);
|
||||
mockUser.Setup(x => x.AllowedSections).Returns(backofficeIdentity.GetAllowedApplications());
|
||||
mockUser.Setup(x => x.Groups).Returns(groups);
|
||||
mockUser.Setup(x => x.Email).Returns("admin@admin.com");
|
||||
mockUser.Setup(x => x.Id).Returns((int)backofficeIdentity.Id);
|
||||
mockUser.Setup(x => x.Id).Returns((int)backofficeIdentity.GetId());
|
||||
mockUser.Setup(x => x.Language).Returns("en");
|
||||
mockUser.Setup(x => x.Name).Returns(backofficeIdentity.RealName);
|
||||
mockUser.Setup(x => x.StartContentIds).Returns(backofficeIdentity.StartContentNodes);
|
||||
mockUser.Setup(x => x.StartMediaIds).Returns(backofficeIdentity.StartMediaNodes);
|
||||
mockUser.Setup(x => x.Username).Returns(backofficeIdentity.Username);
|
||||
mockUser.Setup(x => x.Name).Returns(backofficeIdentity.GetRealName());
|
||||
mockUser.Setup(x => x.StartContentIds).Returns(backofficeIdentity.GetStartContentNodes());
|
||||
mockUser.Setup(x => x.StartMediaIds).Returns(backofficeIdentity.GetStartMediaNodes());
|
||||
mockUser.Setup(x => x.Username).Returns(backofficeIdentity.GetUsername());
|
||||
backofficeSecurity.Setup(x => x.CurrentUser)
|
||||
.Returns(mockUser.Object);
|
||||
|
||||
|
||||
@@ -112,13 +112,13 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
|
||||
return;
|
||||
}
|
||||
|
||||
var identity = actionContext.HttpContext.User.Identity as UmbracoBackOfficeIdentity;
|
||||
var identity = actionContext.HttpContext.User.Identity as ClaimsIdentity;
|
||||
if (identity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Attempt<int> userId = identity.Id.TryConvertTo<int>();
|
||||
Attempt<int> userId = identity.GetId().TryConvertTo<int>();
|
||||
if (userId == false)
|
||||
{
|
||||
return;
|
||||
@@ -133,23 +133,23 @@ namespace Umbraco.Cms.Web.BackOffice.Filters
|
||||
// a list of checks to execute, if any of them pass then we resync
|
||||
var checks = new Func<bool>[]
|
||||
{
|
||||
() => user.Username != identity.Username,
|
||||
() => user.Username != identity.GetUsername(),
|
||||
() =>
|
||||
{
|
||||
CultureInfo culture = user.GetUserCulture(_localizedTextService, _globalSettings.Value);
|
||||
return culture != null && culture.ToString() != identity.Culture;
|
||||
return culture != null && culture.ToString() != identity.GetCultureString();
|
||||
},
|
||||
() => user.AllowedSections.UnsortedSequenceEqual(identity.AllowedApplications) == false,
|
||||
() => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.Roles) == false,
|
||||
() => user.AllowedSections.UnsortedSequenceEqual(identity.GetAllowedApplications()) == false,
|
||||
() => user.Groups.Select(x => x.Alias).UnsortedSequenceEqual(identity.GetRoles()) == false,
|
||||
() =>
|
||||
{
|
||||
var startContentIds = user.CalculateContentStartNodeIds(_entityService);
|
||||
return startContentIds.UnsortedSequenceEqual(identity.StartContentNodes) == false;
|
||||
return startContentIds.UnsortedSequenceEqual(identity.GetStartContentNodes()) == false;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var startMediaIds = user.CalculateMediaStartNodeIds(_entityService);
|
||||
return startMediaIds.UnsortedSequenceEqual(identity.StartMediaNodes) == false;
|
||||
return startMediaIds.UnsortedSequenceEqual(identity.GetStartMediaNodes()) == false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Umbraco.Extensions;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Custom secure format that ensures the Identity in the ticket is <see cref="UmbracoBackOfficeIdentity"/> and not just a ClaimsIdentity
|
||||
/// Custom secure format that ensures the Identity in the ticket is verified <see cref="ClaimsIdentity"/>
|
||||
/// </summary>
|
||||
internal class BackOfficeSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
|
||||
{
|
||||
@@ -59,11 +59,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity((ClaimsIdentity)decrypt.Principal.Identity, out var identity))
|
||||
var identity = (ClaimsIdentity)decrypt.Principal.Identity;
|
||||
if (!identity.VerifyBackOfficeIdentity(out ClaimsIdentity verifiedIdentity))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//return the ticket with a UmbracoBackOfficeIdentity
|
||||
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), decrypt.Properties, decrypt.AuthenticationScheme);
|
||||
var ticket = new AuthenticationTicket(new ClaimsPrincipal(verifiedIdentity), decrypt.Properties, decrypt.AuthenticationScheme);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,9 @@ using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Net;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
{
|
||||
@@ -136,7 +134,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
// Same goes for the signinmanager
|
||||
IBackOfficeSignInManager signInManager = ctx.HttpContext.RequestServices.GetRequiredService<IBackOfficeSignInManager>();
|
||||
|
||||
UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity();
|
||||
ClaimsIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity();
|
||||
if (backOfficeIdentity == null)
|
||||
{
|
||||
ctx.RejectPrincipal();
|
||||
@@ -150,33 +148,33 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
await securityStampValidator.ValidateAsync(ctx);
|
||||
EnsureTicketRenewalIfKeepUserLoggedIn(ctx);
|
||||
|
||||
// add a claim to track when the cookie expires, we use this to track time remaining
|
||||
backOfficeIdentity.AddClaim(new Claim(
|
||||
// add or update a claim to track when the cookie expires, we use this to track time remaining
|
||||
backOfficeIdentity.AddOrUpdateClaim(new Claim(
|
||||
Constants.Security.TicketExpiresClaimType,
|
||||
ctx.Properties.ExpiresUtc.Value.ToString("o"),
|
||||
ClaimValueTypes.DateTime,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
backOfficeIdentity));
|
||||
|
||||
},
|
||||
OnSigningIn = ctx =>
|
||||
{
|
||||
// occurs when sign in is successful but before the ticket is written to the outbound cookie
|
||||
UmbracoBackOfficeIdentity backOfficeIdentity = ctx.Principal.GetUmbracoIdentity();
|
||||
ClaimsIdentity 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
|
||||
Guid session = _runtimeState.Level == RuntimeLevel.Run
|
||||
? _userService.CreateLoginSession(backOfficeIdentity.Id, _ipResolver.GetCurrentRequestIpAddress())
|
||||
? _userService.CreateLoginSession(backOfficeIdentity.GetId(), _ipResolver.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
// add our session claim
|
||||
backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity));
|
||||
backOfficeIdentity.AddClaim(new Claim(Constants.Security.SessionIdClaimType, session.ToString(), ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity));
|
||||
|
||||
// since it is a cookie-based authentication add that claim
|
||||
backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity));
|
||||
backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, backOfficeIdentity));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Extensions
|
||||
/// <returns>
|
||||
/// Returns the current back office identity if an admin is authenticated otherwise null
|
||||
/// </returns>
|
||||
public static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http)
|
||||
public static ClaimsIdentity GetCurrentIdentity(this HttpContext http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException(nameof(http));
|
||||
if (http.User == null) return null; //there's no user at all so no identity
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -176,8 +177,8 @@ namespace Umbraco.Cms.Web.Common.Security
|
||||
|
||||
private string GetCurrentUserId(IPrincipal currentUser)
|
||||
{
|
||||
UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity();
|
||||
var currentUserId = umbIdentity?.GetUserId<string>() ?? Cms.Core.Constants.Security.SuperUserIdAsString;
|
||||
ClaimsIdentity umbIdentity = currentUser?.GetUmbracoIdentity();
|
||||
var currentUserId = umbIdentity?.GetUserId<string>() ?? Core.Constants.Security.SuperUserIdAsString;
|
||||
return currentUserId;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Umbraco.Cms.Web.Common.Security
|
||||
public Attempt<int> GetUserId()
|
||||
{
|
||||
var identity = _httpContextAccessor.HttpContext?.GetCurrentIdentity();
|
||||
return identity == null ? Attempt.Fail<int>() : Attempt.Succeed(identity.Id);
|
||||
return identity == null ? Attempt.Fail<int>() : Attempt.Succeed(identity.GetId());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -39,7 +40,7 @@ namespace Umbraco.Web.Common.Security
|
||||
|
||||
private string GetCurrentUserId(IPrincipal currentUser)
|
||||
{
|
||||
UmbracoBackOfficeIdentity umbIdentity = currentUser?.GetUmbracoIdentity();
|
||||
ClaimsIdentity umbIdentity = currentUser?.GetUmbracoIdentity();
|
||||
var currentUserId = umbIdentity?.GetUserId<string>() ?? Cms.Core.Constants.Security.SuperUserIdAsString;
|
||||
return currentUserId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user