Removes FormsAuthentication cookie format and replaces with standard aspnet identity format, removes a bunch of old obsolete and unused code, fixes the culture setting issue, simplifies the UmbracoBackOfficeIdentity since it no longer needs to be a FormsIdentity and just a straight forward ClaimsIdentity
This commit is contained in:
@@ -1,466 +1,67 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using AutoMapper;
|
||||
using Microsoft.Owin;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Logging;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions to create and renew and remove authentication tickets for the Umbraco back office
|
||||
/// </summary>
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This will check the ticket to see if it is valid, if it is it will set the current thread's user and culture
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="ticket"></param>
|
||||
/// <param name="renewTicket">If true will attempt to renew the ticket</param>
|
||||
public static bool AuthenticateCurrentRequest(this HttpContextBase http, FormsAuthenticationTicket ticket, bool renewTicket)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
|
||||
//if there was a ticket, it's not expired, - it should not be renewed or its renewable
|
||||
if (ticket != null && ticket.Expired == false && (renewTicket == false || http.RenewUmbracoAuthTicket()))
|
||||
{
|
||||
try
|
||||
{
|
||||
//create the Umbraco user identity
|
||||
var identity = new UmbracoBackOfficeIdentity(ticket);
|
||||
|
||||
//set the principal object
|
||||
var principal = new GenericPrincipal(identity, identity.Roles);
|
||||
|
||||
//It is actually not good enough to set this on the current app Context and the thread, it also needs
|
||||
// to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually
|
||||
// an underlying fault of asp.net not propogating the User correctly.
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
HttpContext.Current.User = principal;
|
||||
}
|
||||
http.User = principal;
|
||||
Thread.CurrentPrincipal = principal;
|
||||
|
||||
//This is a back office request, we will also set the culture/ui culture
|
||||
Thread.CurrentThread.CurrentCulture =
|
||||
Thread.CurrentThread.CurrentUICulture =
|
||||
new System.Globalization.CultureInfo(identity.Culture);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is FormatException || ex is JsonReaderException)
|
||||
{
|
||||
//this will occur if the cookie data is invalid
|
||||
http.UmbracoLogout();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will return the current back office identity if the IPrincipal is the correct type
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
internal static UmbracoBackOfficeIdentity GetUmbracoIdentity(this IPrincipal user)
|
||||
{
|
||||
//If it's already a UmbracoBackOfficeIdentity
|
||||
var backOfficeIdentity = user.Identity as UmbracoBackOfficeIdentity;
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
|
||||
//Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that
|
||||
var claimsPrincipal = user as ClaimsPrincipal;
|
||||
if (claimsPrincipal != null)
|
||||
{
|
||||
backOfficeIdentity = claimsPrincipal.Identities.OfType<UmbracoBackOfficeIdentity>().FirstOrDefault();
|
||||
if (backOfficeIdentity != null) return backOfficeIdentity;
|
||||
}
|
||||
|
||||
//Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd and has the back office session
|
||||
var claimsIdentity = user.Identity as ClaimsIdentity;
|
||||
if (claimsIdentity != null && claimsIdentity.IsAuthenticated && claimsIdentity.HasClaim(x => x.Type == Constants.Security.SessionIdClaimType))
|
||||
{
|
||||
try
|
||||
{
|
||||
return UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
/// 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)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (http.User == null) return null; //there's no user at all so no identity
|
||||
|
||||
//If it's already a UmbracoBackOfficeIdentity
|
||||
var backOfficeIdentity = GetUmbracoIdentity(http.User);
|
||||
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>
|
||||
/// <returns>
|
||||
/// Returns the current back office identity if an admin is authenticated otherwise null
|
||||
/// </returns>
|
||||
internal static UmbracoBackOfficeIdentity GetCurrentIdentity(this HttpContext http, bool authenticateRequestIfNotFound)
|
||||
{
|
||||
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, UmbracoConfig.For.UmbracoSettings().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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will force ticket renewal in the OWIN pipeline
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RenewUmbracoAuthTicket(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
http.Items[Constants.Security.ForceReAuthFlag] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will force ticket renewal in the OWIN pipeline
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool RenewUmbracoAuthTicket(this HttpContext http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
http.Items[Constants.Security.ForceReAuthFlag] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the umbraco authentication ticket
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <param name="userdata"></param>
|
||||
public static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata)
|
||||
{
|
||||
//ONLY used by BasePage.doLogin!
|
||||
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
|
||||
var userDataString = JsonConvert.SerializeObject(userdata);
|
||||
return CreateAuthTicketAndCookie(
|
||||
http,
|
||||
userdata.Username,
|
||||
userDataString,
|
||||
//use the configuration timeout - this is the same timeout that will be used when renewing the ticket.
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
//Umbraco has always persisted it's original cookie for 1 day so we'll keep it that way
|
||||
1440,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName,
|
||||
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the number of seconds the user has until their auth session times out
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetRemainingAuthSeconds(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
var ticket = http.GetUmbracoAuthTicket();
|
||||
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>
|
||||
public static double GetRemainingAuthSeconds(this FormsAuthenticationTicket ticket)
|
||||
{
|
||||
if (ticket == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var utcExpired = ticket.Expiration.ToUniversalTime();
|
||||
var secondsRemaining = utcExpired.Subtract(DateTime.UtcNow).TotalSeconds;
|
||||
return secondsRemaining;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the umbraco auth ticket
|
||||
/// </summary>
|
||||
/// <param name="http"></param>
|
||||
/// <returns></returns>
|
||||
public static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContextBase http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
return GetAuthTicket(http, UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName);
|
||||
}
|
||||
|
||||
internal static FormsAuthenticationTicket GetUmbracoAuthTicket(this HttpContext http)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
return new HttpContextWrapper(http).GetUmbracoAuthTicket();
|
||||
}
|
||||
|
||||
internal static FormsAuthenticationTicket GetUmbracoAuthTicket(this IOwinContext ctx)
|
||||
{
|
||||
if (ctx == null) throw new ArgumentNullException("ctx");
|
||||
//get the ticket
|
||||
try
|
||||
{
|
||||
return GetAuthTicket(ctx.Request.Cookies.ToDictionary(x => x.Key, x => x.Value), UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ctx.Authentication.SignOut(
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This will return the current back office identity if the IPrincipal is the correct type
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
internal static UmbracoBackOfficeIdentity GetUmbracoIdentity(this IPrincipal user)
|
||||
{
|
||||
//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 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 and has the back office session
|
||||
if (user.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated && claimsIdentity.HasClaim(x => x.Type == Constants.Security.SessionIdClaimType))
|
||||
{
|
||||
try
|
||||
{
|
||||
return UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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 FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
var asDictionary = new Dictionary<string, string>();
|
||||
for (var i = 0; i < http.Request.Cookies.Keys.Count; i++)
|
||||
{
|
||||
var key = http.Request.Cookies.Keys.Get(i);
|
||||
asDictionary[key] = http.Request.Cookies[key].Value;
|
||||
}
|
||||
|
||||
//get the ticket
|
||||
try
|
||||
{
|
||||
|
||||
return GetAuthTicket(asDictionary, cookieName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//occurs when decryption fails
|
||||
http.Logout(cookieName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FormsAuthenticationTicket GetAuthTicket(IDictionary<string, string> cookies, string cookieName)
|
||||
{
|
||||
if (cookies == null) throw new ArgumentNullException("cookies");
|
||||
|
||||
if (cookies.ContainsKey(cookieName) == false) return null;
|
||||
|
||||
var formsCookie = cookies[cookieName];
|
||||
if (formsCookie == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//get the ticket
|
||||
return FormsAuthentication.Decrypt(formsCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a custom FormsAuthentication ticket with the data specified
|
||||
/// </summary>
|
||||
/// <param name="http">The HTTP.</param>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="userData">The user data.</param>
|
||||
/// <param name="loginTimeoutMins">The login timeout mins.</param>
|
||||
/// <param name="minutesPersisted">The minutes persisted.</param>
|
||||
/// <param name="cookieName">Name of the cookie.</param>
|
||||
/// <param name="cookieDomain">The cookie domain.</param>
|
||||
private static FormsAuthenticationTicket CreateAuthTicketAndCookie(this HttpContextBase http,
|
||||
string username,
|
||||
string userData,
|
||||
int loginTimeoutMins,
|
||||
int minutesPersisted,
|
||||
string cookieName,
|
||||
string cookieDomain)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
// Create a new ticket used for authentication
|
||||
var ticket = new FormsAuthenticationTicket(
|
||||
4,
|
||||
username,
|
||||
DateTime.Now,
|
||||
DateTime.Now.AddMinutes(loginTimeoutMins),
|
||||
true,
|
||||
userData,
|
||||
"/"
|
||||
);
|
||||
|
||||
// Encrypt the cookie using the machine key for secure transport
|
||||
var hash = FormsAuthentication.Encrypt(ticket);
|
||||
var cookie = new HttpCookie(
|
||||
cookieName,
|
||||
hash)
|
||||
{
|
||||
Expires = DateTime.Now.AddMinutes(minutesPersisted),
|
||||
Domain = cookieDomain,
|
||||
Path = "/"
|
||||
};
|
||||
|
||||
if (GlobalSettings.UseSSL)
|
||||
cookie.Secure = true;
|
||||
|
||||
//ensure http only, this should only be able to be accessed via the server
|
||||
cookie.HttpOnly = true;
|
||||
|
||||
http.Response.Cookies.Set(cookie);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the thread culture is set based on the back office user's culture
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
internal 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));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <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>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the thread culture is set based on the back office user's culture
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
internal 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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
@@ -25,22 +26,20 @@ namespace Umbraco.Core.Security
|
||||
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
|
||||
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity,
|
||||
user.Id,
|
||||
user.UserName,
|
||||
user.Name,
|
||||
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
|
||||
new UserData
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.UserName,
|
||||
RealName = user.Name,
|
||||
AllowedApplications = user.AllowedSections,
|
||||
Culture = user.Culture,
|
||||
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
|
||||
StartContentNodes = user.CalculatedContentStartNodeIds,
|
||||
StartMediaNodes = user.CalculatedMediaStartNodeIds,
|
||||
SecurityStamp = user.SecurityStamp
|
||||
});
|
||||
Guid.NewGuid().ToString(),
|
||||
user.SecurityStamp,
|
||||
user.AllowedSections,
|
||||
user.Roles.Select(x => x.RoleId).ToArray());
|
||||
|
||||
return umbracoIdentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory<BackOfficeIdentityUser>
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace Umbraco.Core.Security
|
||||
//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((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
|
||||
? UserService.CreateLoginSession(backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
|
||||
: Guid.NewGuid();
|
||||
|
||||
backOfficeIdentity.UserData.SessionId = session.ToString();
|
||||
}
|
||||
backOfficeIdentity.SessionId = session.ToString();
|
||||
}
|
||||
|
||||
base.ResponseSignIn(context);
|
||||
}
|
||||
|
||||
@@ -31,16 +31,6 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the constructor specifying all dependencies instead")]
|
||||
public BackOfficeUserManager(
|
||||
IUserStore<BackOfficeIdentityUser, int> store,
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
MembershipProviderBase membershipProvider)
|
||||
: this(store, options, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content)
|
||||
{
|
||||
}
|
||||
|
||||
public BackOfficeUserManager(
|
||||
IUserStore<BackOfficeIdentityUser, int> store,
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
@@ -84,17 +74,6 @@ namespace Umbraco.Core.Security
|
||||
return manager;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public static BackOfficeUserManager Create(
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
BackOfficeUserStore customUserStore,
|
||||
MembershipProviderBase membershipProvider)
|
||||
{
|
||||
var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider);
|
||||
return manager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
|
||||
/// </summary>
|
||||
@@ -114,16 +93,6 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
#endregion
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
protected void InitUserManager(
|
||||
BackOfficeUserManager manager,
|
||||
MembershipProviderBase membershipProvider,
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options)
|
||||
{
|
||||
InitUserManager(manager, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the user manager with the correct options
|
||||
/// </summary>
|
||||
@@ -154,7 +123,6 @@ namespace Umbraco.Core.Security
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#region What we support do not currently
|
||||
|
||||
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
|
||||
@@ -183,17 +151,7 @@ namespace Umbraco.Core.Security
|
||||
get { return false; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
protected void InitUserManager(
|
||||
BackOfficeUserManager<T> manager,
|
||||
MembershipProviderBase membershipProvider,
|
||||
IDataProtectionProvider dataProtectionProvider)
|
||||
{
|
||||
InitUserManager(manager, membershipProvider, dataProtectionProvider, UmbracoConfig.For.UmbracoSettings().Content);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the user manager with the correct options
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
@@ -67,5 +68,7 @@ namespace Umbraco.Core.Security
|
||||
return marker.GetManager(owinContext)
|
||||
?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeUserManager<BackOfficeIdentityUser>)} from the {typeof (IOwinContext)}.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Security.Principal;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.Owin.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
@@ -21,10 +22,81 @@ namespace Umbraco.Core.Security
|
||||
/// change over to 'pure' asp.net identity and just inherit from ClaimsIdentity.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class UmbracoBackOfficeIdentity : FormsIdentity
|
||||
public class UmbracoBackOfficeIdentity : ClaimsIdentity
|
||||
{
|
||||
public static UmbracoBackOfficeIdentity FromClaimsIdentity(ClaimsIdentity identity)
|
||||
{
|
||||
return new UmbracoBackOfficeIdentity(identity);
|
||||
}
|
||||
|
||||
/// <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="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)
|
||||
: 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);
|
||||
}
|
||||
|
||||
/// <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="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)
|
||||
: 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
|
||||
@@ -33,145 +105,9 @@ namespace Umbraco.Core.Security
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the required claim " + t + " is missing");
|
||||
}
|
||||
}
|
||||
|
||||
var username = identity.GetUserName();
|
||||
var session = identity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
var securityStamp = identity.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
|
||||
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
|
||||
var startMediaId = identity.FindFirstValue(Constants.Security.StartMediaNodeIdClaimType);
|
||||
|
||||
var culture = identity.FindFirstValue(ClaimTypes.Locality);
|
||||
var id = identity.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var realName = identity.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
if (username == null || startContentId == null || startMediaId == null
|
||||
|| culture == null || id == null
|
||||
|| realName == null || session == null)
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since there are missing required claims");
|
||||
|
||||
int[] startContentIdsAsInt;
|
||||
int[] startMediaIdsAsInt;
|
||||
if (startContentId.DetectIsJson() == false || startMediaId.DetectIsJson() == false)
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids are not JSON");
|
||||
|
||||
try
|
||||
{
|
||||
startContentIdsAsInt = JsonConvert.DeserializeObject<int[]>(startContentId);
|
||||
startMediaIdsAsInt = JsonConvert.DeserializeObject<int[]>(startMediaId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create a " + typeof(UmbracoBackOfficeIdentity) + " from " + typeof(ClaimsIdentity) + " since the data is not formatted correctly - either content or media start Ids could not be parsed as JSON", e);
|
||||
}
|
||||
|
||||
var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList();
|
||||
var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList();
|
||||
|
||||
var userData = new UserData
|
||||
{
|
||||
SecurityStamp = securityStamp,
|
||||
SessionId = session,
|
||||
AllowedApplications = allowedApps.ToArray(),
|
||||
Culture = culture,
|
||||
Id = id,
|
||||
Roles = roles.ToArray(),
|
||||
Username = username,
|
||||
RealName = realName,
|
||||
StartContentNodes = startContentIdsAsInt,
|
||||
StartMediaNodes = startMediaIdsAsInt
|
||||
};
|
||||
|
||||
return new UmbracoBackOfficeIdentity(identity, userData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a back office identity based on user data
|
||||
/// </summary>
|
||||
/// <param name="userdata"></param>
|
||||
public UmbracoBackOfficeIdentity(UserData userdata)
|
||||
//This just creates a temp/fake ticket
|
||||
: base(new FormsAuthenticationTicket(userdata.Username, true, 10))
|
||||
{
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
UserData = userdata;
|
||||
AddUserDataClaims();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a back office identity based on an existing claims identity
|
||||
/// </summary>
|
||||
/// <param name="claimsIdentity"></param>
|
||||
/// <param name="userdata"></param>
|
||||
public UmbracoBackOfficeIdentity(ClaimsIdentity claimsIdentity, UserData userdata)
|
||||
//This just creates a temp/fake ticket
|
||||
: base(new FormsAuthenticationTicket(userdata.Username, true, 10))
|
||||
{
|
||||
if (claimsIdentity == null) throw new ArgumentNullException("claimsIdentity");
|
||||
if (userdata == null) throw new ArgumentNullException("userdata");
|
||||
|
||||
if (claimsIdentity is FormsIdentity)
|
||||
{
|
||||
//since it's a forms auth ticket, it is from a cookie so add that claim
|
||||
AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
|
||||
_currentIssuer = claimsIdentity.AuthenticationType;
|
||||
UserData = userdata;
|
||||
AddExistingClaims(claimsIdentity);
|
||||
Actor = claimsIdentity;
|
||||
AddUserDataClaims();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new identity from a forms auth ticket
|
||||
/// </summary>
|
||||
/// <param name="ticket"></param>
|
||||
public UmbracoBackOfficeIdentity(FormsAuthenticationTicket ticket)
|
||||
: base(ticket)
|
||||
{
|
||||
//since it's a forms auth ticket, it is from a cookie so add that claim
|
||||
AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
UserData = JsonConvert.DeserializeObject<UserData>(ticket.UserData);
|
||||
AddUserDataClaims();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for cloning
|
||||
/// </summary>
|
||||
/// <param name="identity"></param>
|
||||
private UmbracoBackOfficeIdentity(UmbracoBackOfficeIdentity identity)
|
||||
: base(identity)
|
||||
{
|
||||
if (identity.Actor != null)
|
||||
{
|
||||
_currentIssuer = identity.AuthenticationType;
|
||||
AddExistingClaims(identity);
|
||||
Actor = identity.Clone();
|
||||
}
|
||||
|
||||
UserData = identity.UserData;
|
||||
AddUserDataClaims();
|
||||
}
|
||||
|
||||
public const string Issuer = "UmbracoBackOffice";
|
||||
private readonly string _currentIssuer = Issuer;
|
||||
|
||||
/// <summary>
|
||||
/// Used during ctor to add existing claims from an existing ClaimsIdentity
|
||||
/// </summary>
|
||||
/// <param name="claimsIdentity"></param>
|
||||
private void AddExistingClaims(ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
foreach (var claim in claimsIdentity.Claims)
|
||||
{
|
||||
//In one special case we will replace a claim if it exists already and that is the
|
||||
// Forms auth claim for name which automatically gets added
|
||||
TryRemoveClaim(FindFirst(x => x.Type == claim.Type && x.Issuer == "Forms"));
|
||||
|
||||
AddClaim(claim);
|
||||
}
|
||||
}
|
||||
public const string Issuer = Constants.Security.BackOfficeAuthenticationType;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the required claim types for a back office identity
|
||||
@@ -179,153 +115,125 @@ namespace Umbraco.Core.Security
|
||||
/// <remarks>
|
||||
/// This does not incude 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
|
||||
public static IEnumerable<string> RequiredBackOfficeIdentityClaimTypes => new[]
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
ClaimTypes.NameIdentifier, //id
|
||||
ClaimTypes.Name, //username
|
||||
ClaimTypes.GivenName,
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
|
||||
};
|
||||
}
|
||||
}
|
||||
ClaimTypes.NameIdentifier, //id
|
||||
ClaimTypes.Name, //username
|
||||
ClaimTypes.GivenName,
|
||||
Constants.Security.StartContentNodeIdClaimType,
|
||||
Constants.Security.StartMediaNodeIdClaimType,
|
||||
ClaimTypes.Locality,
|
||||
Constants.Security.SessionIdClaimType,
|
||||
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Adds claims based on the UserData data
|
||||
/// Adds claims based on the ctor data
|
||||
/// </summary>
|
||||
private void AddUserDataClaims()
|
||||
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)
|
||||
{
|
||||
//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, UserData.Id.ToString(), ClaimValueTypes.Integer32, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToInvariantString(), ClaimValueTypes.Integer32, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == ClaimTypes.Name) == false)
|
||||
AddClaim(new Claim(ClaimTypes.Name, UserData.Username, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(ClaimTypes.Name, username, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == ClaimTypes.GivenName) == false)
|
||||
AddClaim(new Claim(ClaimTypes.GivenName, UserData.RealName, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(ClaimTypes.GivenName, realName, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
if (HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false)
|
||||
AddClaim(new Claim(Constants.Security.StartContentNodeIdClaimType, JsonConvert.SerializeObject(StartContentNodes), ClaimValueTypes.Integer32, 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)
|
||||
AddClaim(new Claim(Constants.Security.StartMediaNodeIdClaimType, JsonConvert.SerializeObject(StartMediaNodes), 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));
|
||||
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));
|
||||
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 == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
|
||||
//Add each app as a separate claim
|
||||
if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false)
|
||||
if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null)
|
||||
{
|
||||
foreach (var application in AllowedApplications)
|
||||
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 FormsAuthentication ticket so perform the check
|
||||
if (HasClaim(x => x.Type == DefaultRoleClaimType) == false)
|
||||
// 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 based on the UserData
|
||||
foreach (var roleName in UserData.Roles)
|
||||
//manually add them
|
||||
foreach (var roleName in roles)
|
||||
{
|
||||
AddClaim(new Claim(RoleClaimType, roleName, ClaimValueTypes.String, Issuer, Issuer, this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected internal UserData UserData { get; private set; }
|
||||
|
||||
|
||||
/// <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
|
||||
{
|
||||
get { return _currentIssuer; }
|
||||
}
|
||||
public override string AuthenticationType => Issuer;
|
||||
|
||||
public int[] StartContentNodes
|
||||
{
|
||||
get { return UserData.StartContentNodes; }
|
||||
}
|
||||
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());
|
||||
|
||||
public int[] StartMediaNodes
|
||||
{
|
||||
get { return UserData.StartMediaNodes; }
|
||||
}
|
||||
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());
|
||||
|
||||
public string[] AllowedApplications
|
||||
{
|
||||
get { return UserData.AllowedApplications; }
|
||||
}
|
||||
private string[] _allowedApplications;
|
||||
public string[] AllowedApplications => _allowedApplications ?? (_allowedApplications = FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray());
|
||||
|
||||
public object Id
|
||||
{
|
||||
get { return UserData.Id; }
|
||||
}
|
||||
public int Id => int.Parse(this.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||
|
||||
public string RealName
|
||||
{
|
||||
get { return UserData.RealName; }
|
||||
}
|
||||
public string RealName => this.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
public string Username
|
||||
{
|
||||
get { return UserData.Username; }
|
||||
}
|
||||
public string Username => this.GetUserName();
|
||||
|
||||
public string Culture
|
||||
{
|
||||
get { return UserData.Culture; }
|
||||
}
|
||||
public string Culture => this.FindFirstValue(ClaimTypes.Locality);
|
||||
|
||||
public string SessionId
|
||||
{
|
||||
get { return UserData.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
|
||||
{
|
||||
get { return UserData.SecurityStamp; }
|
||||
}
|
||||
|
||||
public string[] Roles
|
||||
{
|
||||
get { return UserData.Roles; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a copy of the current <see cref="T:UmbracoBackOfficeIdentity"/> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A copy of the current <see cref="T:UmbracoBackOfficeIdentity"/> instance.
|
||||
/// </returns>
|
||||
public override ClaimsIdentity Clone()
|
||||
{
|
||||
return new UmbracoBackOfficeIdentity(this);
|
||||
}
|
||||
public string SecurityStamp => this.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
|
||||
|
||||
public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Data structure used to store information in the authentication cookie
|
||||
/// </summary>
|
||||
[DataContract(Name = "userData", Namespace = "")]
|
||||
[Serializable]
|
||||
public class UserData
|
||||
{
|
||||
public UserData()
|
||||
{
|
||||
AllowedApplications = new string[] {};
|
||||
Roles = new string[] {};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this constructor to create/assign new UserData to the ticket
|
||||
/// </summary>
|
||||
/// <param name="sessionId">
|
||||
/// The current sessionId for the user
|
||||
/// </param>
|
||||
public UserData(string sessionId)
|
||||
{
|
||||
SessionId = sessionId;
|
||||
AllowedApplications = new string[] { };
|
||||
Roles = new string[] { };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session identifier.
|
||||
/// </summary>
|
||||
[DataMember(Name = "sessionId")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the security stamp.
|
||||
/// </summary>
|
||||
[DataMember(Name = "securityStamp")]
|
||||
public string SecurityStamp { get; set; }
|
||||
|
||||
[DataMember(Name = "id")]
|
||||
public object Id { get; set; }
|
||||
|
||||
[DataMember(Name = "roles")]
|
||||
public string[] Roles { get; set; }
|
||||
|
||||
[DataMember(Name = "username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string RealName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start nodes on the UserData object for the auth ticket contains all of the user's start nodes including ones assigned to their user groups
|
||||
/// </summary>
|
||||
[DataMember(Name = "startContent")]
|
||||
public int[] StartContentNodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start nodes on the UserData object for the auth ticket contains all of the user's start nodes including ones assigned to their user groups
|
||||
/// </summary>
|
||||
[DataMember(Name = "startMedia")]
|
||||
public int[] StartMediaNodes { get; set; }
|
||||
|
||||
[DataMember(Name = "allowedApps")]
|
||||
public string[] AllowedApplications { get; set; }
|
||||
|
||||
[DataMember(Name = "culture")]
|
||||
public string Culture { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user