Merge pull request #663 from Shazwazza/dev-v7-7.30-AspNetIdentity
ASP.Net Identity for back office users
This commit is contained in:
18
LICENSE.md
18
LICENSE.md
@@ -1,9 +1,9 @@
|
||||
# The MIT License (MIT) #
|
||||
|
||||
Copyright (c) 2013 Umbraco
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
# The MIT License (MIT) #
|
||||
|
||||
Copyright (c) 2013 Umbraco
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
<Message Text="Finished compiling projects" Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="SetVersionNumber" Condition="'$(BUILD_RELEASE)'!=''">
|
||||
<PropertyGroup>
|
||||
<NewVersion>$(BUILD_RELEASE)</NewVersion>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>UmbracoExamine.PDF</id>
|
||||
<version>0.7.0</version>
|
||||
<authors>Umbraco HQ</authors>
|
||||
<owners>Umbraco HQ</owners>
|
||||
<licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>
|
||||
<projectUrl>http://umbraco.com/</projectUrl>
|
||||
<iconUrl>http://umbraco.com/media/357769/100px_transparent.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>UmbracoExmine.PDF</description>
|
||||
<tags>umbraco</tags>
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[7.0.0, 8.0.0)" />
|
||||
<dependency id="iTextSharp" version="[5.5.3, 6.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\_BuildOutput\UmbracoExamine.PDF\UmbracoExamine.PDF.dll" target="lib\UmbracoExamine.PDF.dll" />
|
||||
<file src="..\_BuildOutput\UmbracoExamine.PDF\UmbracoExamine.PDF.XML" target="lib\UmbracoExamine.PDF.XML" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -77,8 +77,9 @@
|
||||
<Compile Include="SqlCEUtility.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="app.config" />
|
||||
<None Include="app.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.2.5")]
|
||||
[assembly: AssemblyInformationalVersion("7.2.5")]
|
||||
[assembly: AssemblyFileVersion("7.3.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.3.0")]
|
||||
@@ -18,5 +18,30 @@
|
||||
public const string AuthCookieName = "UMB_UCONTEXT";
|
||||
|
||||
}
|
||||
|
||||
public static class Security
|
||||
{
|
||||
|
||||
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
|
||||
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
|
||||
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used for external identity providers for their authentication type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default we don't want to interfere with front-end external providers and their default setup, for back office the
|
||||
/// providers need to be setup differently and each auth type for the back office will be prefixed with this value
|
||||
/// </remarks>
|
||||
public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco.";
|
||||
|
||||
public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
|
||||
public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
|
||||
public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapps";
|
||||
//public const string UserIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/userid";
|
||||
public const string CultureClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/culture";
|
||||
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
Normal file
75
src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
public class BackOfficeIdentityUser : IdentityUser<int, IIdentityUserLogin, IdentityUserRole<string>, IdentityUserClaim<int>>
|
||||
{
|
||||
|
||||
public virtual async Task<ClaimsIdentity> GenerateUserIdentityAsync(BackOfficeUserManager manager)
|
||||
{
|
||||
// NOTE the authenticationType must match the umbraco one
|
||||
// defined in CookieAuthenticationOptions.AuthenticationType
|
||||
var userIdentity = await manager.CreateIdentityAsync(this, Constants.Security.BackOfficeAuthenticationType);
|
||||
return userIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the user's real name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public int StartContentId { get; set; }
|
||||
public int StartMediaId { get; set; }
|
||||
public string[] AllowedSections { get; set; }
|
||||
public string Culture { get; set; }
|
||||
|
||||
public string UserTypeAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to make the retrieval lazy
|
||||
/// </summary>
|
||||
public override ICollection<IIdentityUserLogin> Logins
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_getLogins != null && _getLogins.IsValueCreated == false)
|
||||
{
|
||||
_logins = new ObservableCollection<IIdentityUserLogin>();
|
||||
foreach (var l in _getLogins.Value)
|
||||
{
|
||||
_logins.Add(l);
|
||||
}
|
||||
//now assign events
|
||||
_logins.CollectionChanged += Logins_CollectionChanged;
|
||||
}
|
||||
return _logins;
|
||||
}
|
||||
}
|
||||
|
||||
public bool LoginsChanged { get; private set; }
|
||||
|
||||
void Logins_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
LoginsChanged = true;
|
||||
}
|
||||
|
||||
private ObservableCollection<IIdentityUserLogin> _logins;
|
||||
private Lazy<IEnumerable<IIdentityUserLogin>> _getLogins;
|
||||
|
||||
/// <summary>
|
||||
/// Used to set a lazy call back to populate the user's Login list
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public void SetLoginsCallback(Lazy<IEnumerable<IIdentityUserLogin>> callback)
|
||||
{
|
||||
if (callback == null) throw new ArgumentNullException("callback");
|
||||
_getLogins = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
Normal file
25
src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
public interface IIdentityUserLogin : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// The login provider for the login (i.e. facebook, google)
|
||||
///
|
||||
/// </summary>
|
||||
string LoginProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key representing the login for the provider
|
||||
///
|
||||
/// </summary>
|
||||
string ProviderKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User Id for the user who owns this login
|
||||
///
|
||||
/// </summary>
|
||||
int UserId { get; set; }
|
||||
}
|
||||
}
|
||||
34
src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
Normal file
34
src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
public class IdentityModelMappings : MapperConfiguration
|
||||
{
|
||||
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
|
||||
{
|
||||
config.CreateMap<IUser, BackOfficeIdentityUser>()
|
||||
.ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
|
||||
.ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
|
||||
.ForMember(user => user.LockoutEnabled, expression => expression.MapFrom(user => user.IsLockedOut))
|
||||
.ForMember(user => user.LockoutEndDateUtc, expression => expression.UseValue(DateTime.MaxValue.ToUniversalTime()))
|
||||
.ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username))
|
||||
.ForMember(user => user.PasswordHash, expression => expression.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
|
||||
.ForMember(user => user.Culture, expression => expression.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)))
|
||||
.ForMember(user => user.Name, expression => expression.MapFrom(user => user.Name))
|
||||
.ForMember(user => user.StartMediaId, expression => expression.MapFrom(user => user.StartMediaId))
|
||||
.ForMember(user => user.StartContentId, expression => expression.MapFrom(user => user.StartContentId))
|
||||
.ForMember(user => user.UserTypeAlias, expression => expression.MapFrom(user => user.UserType.Alias))
|
||||
.ForMember(user => user.AllowedSections, expression => expression.MapFrom(user => user.AllowedSections.ToArray()));
|
||||
}
|
||||
|
||||
private string GetPasswordHash(string storedPass)
|
||||
{
|
||||
return storedPass.StartsWith("___UIDEMPTYPWORD__") ? null : storedPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
src/Umbraco.Core/Models/Identity/IdentityUser.cs
Normal file
125
src/Umbraco.Core/Models/Identity/IdentityUser.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Default IUser implementation
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"/><typeparam name="TLogin"/><typeparam name="TRole"/><typeparam name="TClaim"/>
|
||||
/// <remarks>
|
||||
/// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want
|
||||
/// references to that so we will create our own here
|
||||
/// </remarks>
|
||||
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
|
||||
where TLogin : IIdentityUserLogin
|
||||
//NOTE: Making our role id a string
|
||||
where TRole : IdentityUserRole<string>
|
||||
where TClaim : IdentityUserClaim<TKey>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
public IdentityUser()
|
||||
{
|
||||
this.Claims = new List<TClaim>();
|
||||
this.Roles = new List<TRole>();
|
||||
this.Logins = new List<TLogin>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Email
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the email is confirmed, default is false
|
||||
///
|
||||
/// </summary>
|
||||
public virtual bool EmailConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The salted/hashed form of the user password
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string PasswordHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A random value that should change whenever a users credentials have changed (password changed, login removed)
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string SecurityStamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PhoneNumber for the user
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string PhoneNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the phone number is confirmed, default is false
|
||||
///
|
||||
/// </summary>
|
||||
public virtual bool PhoneNumberConfirmed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is two factor enabled for the user
|
||||
///
|
||||
/// </summary>
|
||||
public virtual bool TwoFactorEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DateTime in UTC when lockout ends, any time in the past is considered not locked out.
|
||||
///
|
||||
/// </summary>
|
||||
public virtual DateTime? LockoutEndDateUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is lockout enabled for this user
|
||||
///
|
||||
/// </summary>
|
||||
public virtual bool LockoutEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to record failures for the purposes of lockout
|
||||
///
|
||||
/// </summary>
|
||||
public virtual int AccessFailedCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Navigation property for user roles
|
||||
///
|
||||
/// </summary>
|
||||
public virtual ICollection<TRole> Roles { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Navigation property for user claims
|
||||
///
|
||||
/// </summary>
|
||||
public virtual ICollection<TClaim> Claims { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Navigation property for user logins
|
||||
///
|
||||
/// </summary>
|
||||
public virtual ICollection<TLogin> Logins { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// User ID (Primary Key)
|
||||
///
|
||||
/// </summary>
|
||||
public virtual TKey Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User name
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string UserName { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs
Normal file
38
src/Umbraco.Core/Models/Identity/IdentityUserClaim.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// EntityType that represents one specific user claim
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"/>
|
||||
/// <remarks>
|
||||
/// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want
|
||||
/// references to that so we will create our own here
|
||||
/// </remarks>
|
||||
public class IdentityUserClaim<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Primary key
|
||||
///
|
||||
/// </summary>
|
||||
public virtual int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User Id for the user who owns this login
|
||||
///
|
||||
/// </summary>
|
||||
public virtual TKey UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Claim type
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string ClaimType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Claim value
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string ClaimValue { get; set; }
|
||||
}
|
||||
}
|
||||
46
src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
Normal file
46
src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity type for a user's login (i.e. facebook, google)
|
||||
///
|
||||
/// </summary>
|
||||
public class IdentityUserLogin : Entity, IIdentityUserLogin
|
||||
{
|
||||
public IdentityUserLogin(string loginProvider, string providerKey, int userId)
|
||||
{
|
||||
LoginProvider = loginProvider;
|
||||
ProviderKey = providerKey;
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public IdentityUserLogin(int id, string loginProvider, string providerKey, int userId, DateTime createDate)
|
||||
{
|
||||
Id = id;
|
||||
LoginProvider = loginProvider;
|
||||
ProviderKey = providerKey;
|
||||
UserId = userId;
|
||||
CreateDate = createDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The login provider for the login (i.e. facebook, google)
|
||||
///
|
||||
/// </summary>
|
||||
public string LoginProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key representing the login for the provider
|
||||
///
|
||||
/// </summary>
|
||||
public string ProviderKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User Id for the user who owns this login
|
||||
///
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
}
|
||||
}
|
||||
26
src/Umbraco.Core/Models/Identity/IdentityUserRole.cs
Normal file
26
src/Umbraco.Core/Models/Identity/IdentityUserRole.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Umbraco.Core.Models.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// EntityType that represents a user belonging to a role
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"/>
|
||||
/// <remarks>
|
||||
/// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want
|
||||
/// references to that so we will create our own here
|
||||
/// </remarks>
|
||||
public class IdentityUserRole<TKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// UserId for the user that is in the role
|
||||
///
|
||||
/// </summary>
|
||||
public virtual TKey UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RoleId for the role
|
||||
///
|
||||
/// </summary>
|
||||
public virtual TKey RoleId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,6 @@ namespace Umbraco.Core.Models.Mapping
|
||||
{
|
||||
public abstract void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -38,5 +38,10 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// Exposes the basic profile data
|
||||
/// </summary>
|
||||
IProfile ProfileData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The security stamp used by ASP.Net identity
|
||||
/// </summary>
|
||||
string SecurityStamp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
|
||||
private IUserType _userType;
|
||||
private string _name;
|
||||
private string _securityStamp;
|
||||
private List<string> _addedSections;
|
||||
private List<string> _removedSections;
|
||||
private ObservableCollection<string> _sectionCollection;
|
||||
@@ -76,6 +77,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
|
||||
private bool _defaultToLiveEditing;
|
||||
|
||||
private static readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
|
||||
private static readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
|
||||
private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartContentId);
|
||||
private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartMediaId);
|
||||
@@ -232,6 +234,22 @@ namespace Umbraco.Core.Models.Membership
|
||||
get { return new UserProfile(this); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The security stamp used by ASP.Net identity
|
||||
/// </summary>
|
||||
public string SecurityStamp
|
||||
{
|
||||
get { return _securityStamp; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_securityStamp = value;
|
||||
return _securityStamp;
|
||||
}, _securityStamp, SecurityStampSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used internally to check if we need to add a section in the repository to the db
|
||||
/// </summary>
|
||||
|
||||
35
src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs
Normal file
35
src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoExternalLogin")]
|
||||
[ExplicitColumns]
|
||||
[PrimaryKey("Id")]
|
||||
internal class ExternalLoginDto
|
||||
{
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_umbracoExternalLogin")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("userId")]
|
||||
public int UserId { get; set; }
|
||||
|
||||
[Column("loginProvider")]
|
||||
[Length(4000)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string LoginProvider { get; set; }
|
||||
|
||||
[Column("providerKey")]
|
||||
[Length(4000)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string ProviderKey { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Constraint(Default = "getdate()")]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,11 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(10)]
|
||||
public string UserLanguage { get; set; }
|
||||
|
||||
[Column("securityStampToken")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(255)]
|
||||
public string SecurityStampToken { get; set; }
|
||||
|
||||
[ResultColumn]
|
||||
public List<User2AppDto> User2AppDtos { get; set; }
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
internal class ExternalLoginFactory
|
||||
{
|
||||
public IIdentityUserLogin BuildEntity(ExternalLoginDto dto)
|
||||
{
|
||||
var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate);
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
entity.ResetDirtyProperties(false);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public ExternalLoginDto BuildDto(IIdentityUserLogin entity)
|
||||
{
|
||||
var dto = new ExternalLoginDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
CreateDate = entity.CreateDate,
|
||||
LoginProvider = entity.LoginProvider,
|
||||
ProviderKey = entity.ProviderKey,
|
||||
UserId = entity.UserId
|
||||
};
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -32,7 +33,9 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
IsLockedOut = dto.NoConsole,
|
||||
IsApproved = dto.Disabled == false,
|
||||
Email = dto.Email,
|
||||
Language = dto.UserLanguage
|
||||
Language = dto.UserLanguage,
|
||||
//make it a GUID if it's empty
|
||||
SecurityStamp = dto.SecurityStampToken.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString() : dto.SecurityStampToken
|
||||
};
|
||||
|
||||
foreach (var app in dto.User2AppDtos)
|
||||
@@ -61,7 +64,8 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
UserLanguage = entity.Language,
|
||||
UserName = entity.Name,
|
||||
Type = short.Parse(entity.UserType.Id.ToString(CultureInfo.InvariantCulture)),
|
||||
User2AppDtos = new List<User2AppDto>()
|
||||
User2AppDtos = new List<User2AppDto>(),
|
||||
SecurityStampToken = entity.SecurityStamp
|
||||
};
|
||||
|
||||
foreach (var app in entity.AllowedSections)
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
[MapperFor(typeof(IIdentityUserLogin))]
|
||||
[MapperFor(typeof(IdentityUserLogin))]
|
||||
public sealed class ExternalLoginMapper : BaseMapper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
|
||||
public ExternalLoginMapper()
|
||||
{
|
||||
BuildMap();
|
||||
}
|
||||
|
||||
#region Overrides of BaseMapper
|
||||
|
||||
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache
|
||||
{
|
||||
get { return PropertyInfoCacheInstance; }
|
||||
}
|
||||
|
||||
internal override void BuildMap()
|
||||
{
|
||||
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.Id, dto => dto.Id);
|
||||
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.CreateDate, dto => dto.CreateDate);
|
||||
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.LoginProvider, dto => dto.LoginProvider);
|
||||
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.ProviderKey, dto => dto.ProviderKey);
|
||||
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.UserId, dto => dto.UserId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[MapperFor(typeof(IUser))]
|
||||
[MapperFor(typeof(User))]
|
||||
public sealed class UserMapper : BaseMapper
|
||||
|
||||
@@ -81,7 +81,9 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{40, typeof (ServerRegistrationDto)},
|
||||
|
||||
{41, typeof (AccessDto)},
|
||||
{42, typeof (AccessRuleDto)}
|
||||
{42, typeof (AccessRuleDto)},
|
||||
|
||||
{43, typeof (ExternalLoginDto)}
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -98,6 +98,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
return new Version(7, 0, 0);
|
||||
}
|
||||
|
||||
//if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added
|
||||
if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess"))))
|
||||
{
|
||||
return new Version(7, 2, 5);
|
||||
}
|
||||
|
||||
return UmbracoVersion.Current;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
|
||||
{
|
||||
[Migration("7.3.0", 9, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddExternalLoginsTable : MigrationBase
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the table is already there
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
if (tables.InvariantContains("umbracoExternalLogin")) return;
|
||||
|
||||
Create.Table("umbracoExternalLogin")
|
||||
.WithColumn("id").AsInt32().Identity().PrimaryKey("PK_umbracoExternalLogin")
|
||||
.WithColumn("userId").AsInt32().NotNullable().ForeignKey("FK_umbracoExternalLogin_umbracoUser_id", "umbracoUser", "id")
|
||||
.WithColumn("loginProvider").AsString(4000).NotNullable()
|
||||
.WithColumn("providerKey").AsString(4000).NotNullable()
|
||||
.WithColumn("createDate").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime);
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZero
|
||||
{
|
||||
[Migration("7.3.0", 10, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddUserSecurityStampColumn : MigrationBase
|
||||
{
|
||||
public override void Up()
|
||||
{
|
||||
//Don't exeucte if the column is already there
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("securityStampToken"))) return;
|
||||
|
||||
Create.Column("securityStampToken").OnTable("umbracoUser").AsString(255).Nullable();
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Factories;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class ExternalLoginRepository : PetaPocoRepositoryBase<int, IIdentityUserLogin>, IExternalLoginRepository
|
||||
{
|
||||
public ExternalLoginRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
|
||||
public void DeleteUserLogins(int memberId)
|
||||
{
|
||||
using (var t = Database.GetTransaction())
|
||||
{
|
||||
Database.Execute("DELETE FROM ExternalLogins WHERE UserId=@userId", new { userId = memberId });
|
||||
|
||||
t.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveUserLogins(int memberId, IEnumerable<UserLoginInfo> logins)
|
||||
{
|
||||
using (var t = Database.GetTransaction())
|
||||
{
|
||||
//clear out logins for member
|
||||
Database.Execute("DELETE FROM umbracoExternalLogin WHERE userId=@userId", new { userId = memberId });
|
||||
|
||||
//add them all
|
||||
foreach (var l in logins)
|
||||
{
|
||||
Database.Insert(new ExternalLoginDto
|
||||
{
|
||||
LoginProvider = l.LoginProvider,
|
||||
ProviderKey = l.ProviderKey,
|
||||
UserId = memberId,
|
||||
CreateDate = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
t.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
protected override IIdentityUserLogin PerformGet(int id)
|
||||
{
|
||||
var sql = GetBaseQuery(false);
|
||||
sql.Where(GetBaseWhereClause(), new { Id = id });
|
||||
|
||||
var macroDto = Database.Fetch<ExternalLoginDto>(sql).FirstOrDefault();
|
||||
if (macroDto == null)
|
||||
return null;
|
||||
|
||||
var factory = new ExternalLoginFactory();
|
||||
var entity = factory.BuildEntity(macroDto);
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
protected override IEnumerable<IIdentityUserLogin> PerformGetAll(params int[] ids)
|
||||
{
|
||||
if (ids.Any())
|
||||
{
|
||||
return PerformGetAllOnIds(ids);
|
||||
}
|
||||
|
||||
var sql = GetBaseQuery(false);
|
||||
|
||||
return ConvertFromDtos(Database.Fetch<ExternalLoginDto>(sql))
|
||||
.ToArray();// we don't want to re-iterate again!
|
||||
}
|
||||
|
||||
private IEnumerable<IIdentityUserLogin> PerformGetAllOnIds(params int[] ids)
|
||||
{
|
||||
if (ids.Any() == false) yield break;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
yield return Get(id);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IIdentityUserLogin> ConvertFromDtos(IEnumerable<ExternalLoginDto> dtos)
|
||||
{
|
||||
var factory = new ExternalLoginFactory();
|
||||
foreach (var entity in dtos.Select(factory.BuildEntity))
|
||||
{
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
|
||||
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<IIdentityUserLogin> PerformGetByQuery(IQuery<IIdentityUserLogin> query)
|
||||
{
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<IIdentityUserLogin>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
|
||||
var dtos = Database.Fetch<ExternalLoginDto>(sql);
|
||||
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
yield return Get(dto.Id);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
var sql = new Sql();
|
||||
if (isCount)
|
||||
{
|
||||
sql.Select("COUNT(*)").From<ExternalLoginDto>(SqlSyntax);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql.Select("*").From<ExternalLoginDto>(SqlSyntax);
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected override string GetBaseWhereClause()
|
||||
{
|
||||
return "umbracoExternalLogin.id = @Id";
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
var list = new List<string>
|
||||
{
|
||||
"DELETE FROM umbracoExternalLogin WHERE id = @Id"
|
||||
};
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override Guid NodeObjectTypeId
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
protected override void PersistNewItem(IIdentityUserLogin entity)
|
||||
{
|
||||
((Entity)entity).AddingEntity();
|
||||
|
||||
var factory = new ExternalLoginFactory();
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
entity.Id = id;
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(IIdentityUserLogin entity)
|
||||
{
|
||||
((Entity)entity).UpdatingEntity();
|
||||
|
||||
var factory = new ExternalLoginFactory();
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
Database.Update(dto);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IExternalLoginRepository : IRepositoryQueryable<int, IIdentityUserLogin>
|
||||
{
|
||||
void SaveUserLogins(int memberId, IEnumerable<UserLoginInfo> logins);
|
||||
void DeleteUserLogins(int memberId);
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
"DELETE FROM umbracoUser2NodeNotify WHERE userId = @Id",
|
||||
"DELETE FROM umbracoUserLogins WHERE userID = @Id",
|
||||
"DELETE FROM umbracoUser2app WHERE " + SqlSyntax.GetQuotedColumnName("user") + "=@Id",
|
||||
"DELETE FROM umbracoUser WHERE id = @Id"
|
||||
"DELETE FROM umbracoUser WHERE id = @Id",
|
||||
"DELETE FROM umbracoExternalLogin WHERE id = @Id"
|
||||
};
|
||||
return list;
|
||||
}
|
||||
@@ -169,7 +170,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{"userName", "Name"},
|
||||
{"userLogin", "Username"},
|
||||
{"userEmail", "Email"},
|
||||
{"userLanguage", "Language"}
|
||||
{"userLanguage", "Language"},
|
||||
{"securityStampToken", "SecurityStamp"}
|
||||
};
|
||||
|
||||
//create list of properties that have changed
|
||||
@@ -182,6 +184,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
changedCols.Add("userPassword");
|
||||
|
||||
//special case - when using ASP.Net identity the user manager will take care of updating the security stamp, however
|
||||
// when not using ASP.Net identity (i.e. old membership providers), we'll need to take care of updating this manually
|
||||
// so we can just detect if that property is dirty, if it's not we'll set it manually
|
||||
if (dirtyEntity.IsPropertyDirty("SecurityStamp") == false)
|
||||
{
|
||||
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
||||
changedCols.Add("securityStampToken");
|
||||
}
|
||||
}
|
||||
|
||||
//only update the changed cols
|
||||
|
||||
@@ -64,6 +64,13 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual IExternalLoginRepository CreateExternalLoginRepository(IDatabaseUnitOfWork uow)
|
||||
{
|
||||
return new ExternalLoginRepository(uow,
|
||||
_cacheHelper,
|
||||
_logger, _sqlSyntax);
|
||||
}
|
||||
|
||||
public virtual IPublicAccessRepository CreatePublicAccessRepository(IDatabaseUnitOfWork uow)
|
||||
{
|
||||
return new PublicAccessRepository(uow,
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Microsoft.Owin;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
@@ -158,8 +165,60 @@ namespace Umbraco.Core.Security
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
};
|
||||
//remove the external login cookie too
|
||||
var extLoginCookie = new CookieHeaderValue(Constants.Security.BackOfficeExternalCookieName, "")
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
};
|
||||
|
||||
response.Headers.AddCookies(new[] { authCookie, prevCookie });
|
||||
response.Headers.AddCookies(new[] { authCookie, prevCookie, extLoginCookie });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This adds the forms authentication cookie for webapi since cookies are handled differently
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="user"></param>
|
||||
public static FormsAuthenticationTicket UmbracoLoginWebApi(this HttpResponseMessage response, IUser user)
|
||||
{
|
||||
if (response == null) throw new ArgumentNullException("response");
|
||||
|
||||
//remove the external login cookie
|
||||
var extLoginCookie = new CookieHeaderValue(Constants.Security.BackOfficeExternalCookieName, "")
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
Path = "/"
|
||||
};
|
||||
|
||||
var userDataString = JsonConvert.SerializeObject(Mapper.Map<UserData>(user));
|
||||
|
||||
var ticket = new FormsAuthenticationTicket(
|
||||
4,
|
||||
user.Username,
|
||||
DateTime.Now,
|
||||
DateTime.Now.AddMinutes(GlobalSettings.TimeOutInMinutes),
|
||||
true,
|
||||
userDataString,
|
||||
"/"
|
||||
);
|
||||
|
||||
// Encrypt the cookie using the machine key for secure transport
|
||||
var encrypted = FormsAuthentication.Encrypt(ticket);
|
||||
|
||||
//add the cookie
|
||||
var authCookie = new CookieHeaderValue(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, encrypted)
|
||||
{
|
||||
//Umbraco has always persisted it's original cookie for 1 day so we'll keep it that way
|
||||
Expires = DateTime.Now.AddMinutes(1440),
|
||||
Path = "/",
|
||||
Secure = GlobalSettings.UseSSL,
|
||||
HttpOnly = true
|
||||
};
|
||||
|
||||
response.Headers.AddCookies(new[] { authCookie, extLoginCookie });
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,7 +270,6 @@ namespace Umbraco.Core.Security
|
||||
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);
|
||||
}
|
||||
@@ -268,6 +326,23 @@ namespace Umbraco.Core.Security
|
||||
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)
|
||||
{
|
||||
//TODO: Do we need to do more here?? need to make sure that the forms cookie is gone, but is that
|
||||
// taken care of in our custom middleware somehow?
|
||||
ctx.Authentication.SignOut();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This clears the forms authentication cookie
|
||||
/// </summary>
|
||||
@@ -276,8 +351,8 @@ namespace Umbraco.Core.Security
|
||||
private static void Logout(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
//clear the preview cookie too
|
||||
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName };
|
||||
//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
|
||||
@@ -301,16 +376,18 @@ namespace Umbraco.Core.Security
|
||||
|
||||
private static FormsAuthenticationTicket GetAuthTicket(this HttpContextBase http, string cookieName)
|
||||
{
|
||||
if (http == null) throw new ArgumentNullException("http");
|
||||
var formsCookie = http.Request.Cookies[cookieName];
|
||||
if (formsCookie == null)
|
||||
var asDictionary = new Dictionary<string, string>();
|
||||
for (var i = 0; i < http.Request.Cookies.Keys.Count; i++)
|
||||
{
|
||||
return null;
|
||||
var key = http.Request.Cookies.Keys.Get(i);
|
||||
asDictionary[key] = http.Request.Cookies[key].Value;
|
||||
}
|
||||
|
||||
//get the ticket
|
||||
try
|
||||
{
|
||||
return FormsAuthentication.Decrypt(formsCookie.Value);
|
||||
|
||||
return GetAuthTicket(asDictionary, cookieName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -320,6 +397,21 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// Renews the forms authentication ticket & cookie
|
||||
/// </summary>
|
||||
@@ -373,7 +465,6 @@ namespace Umbraco.Core.Security
|
||||
/// <param name="userData">The user data.</param>
|
||||
/// <param name="loginTimeoutMins">The login timeout mins.</param>
|
||||
/// <param name="minutesPersisted">The minutes persisted.</param>
|
||||
/// <param name="cookiePath">The cookie path.</param>
|
||||
/// <param name="cookieName">Name of the cookie.</param>
|
||||
/// <param name="cookieDomain">The cookie domain.</param>
|
||||
private static FormsAuthenticationTicket CreateAuthTicketAndCookie(this HttpContextBase http,
|
||||
@@ -381,7 +472,6 @@ namespace Umbraco.Core.Security
|
||||
string userData,
|
||||
int loginTimeoutMins,
|
||||
int minutesPersisted,
|
||||
string cookiePath,
|
||||
string cookieName,
|
||||
string cookieDomain)
|
||||
{
|
||||
@@ -394,7 +484,7 @@ namespace Umbraco.Core.Security
|
||||
DateTime.Now.AddMinutes(loginTimeoutMins),
|
||||
true,
|
||||
userData,
|
||||
cookiePath
|
||||
"/"
|
||||
);
|
||||
|
||||
// Encrypt the cookie using the machine key for secure transport
|
||||
@@ -404,7 +494,8 @@ namespace Umbraco.Core.Security
|
||||
hash)
|
||||
{
|
||||
Expires = DateTime.Now.AddMinutes(minutesPersisted),
|
||||
Domain = cookieDomain
|
||||
Domain = cookieDomain,
|
||||
Path = "/"
|
||||
};
|
||||
|
||||
if (GlobalSettings.UseSSL)
|
||||
|
||||
35
src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs
Normal file
35
src/Umbraco.Core/Security/BackOfficeClaimsIdentityFactory.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory<BackOfficeIdentityUser, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a ClaimsIdentity from a user
|
||||
/// </summary>
|
||||
/// <param name="manager"/><param name="user"/><param name="authenticationType"/>
|
||||
/// <returns/>
|
||||
public override async Task<ClaimsIdentity> CreateAsync(UserManager<BackOfficeIdentityUser, int> manager, BackOfficeIdentityUser user, string authenticationType)
|
||||
{
|
||||
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
|
||||
|
||||
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity, 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(),
|
||||
StartContentNode = user.StartContentId,
|
||||
StartMediaNode = user.StartMediaId
|
||||
});
|
||||
|
||||
return umbracoIdentity;
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/Umbraco.Core/Security/BackOfficeUserManager.cs
Normal file
172
src/Umbraco.Core/Security/BackOfficeUserManager.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Default back office user manager
|
||||
/// </summary>
|
||||
public class BackOfficeUserManager : BackOfficeUserManager<BackOfficeIdentityUser>
|
||||
{
|
||||
public BackOfficeUserManager(IUserStore<BackOfficeIdentityUser, int> store)
|
||||
: base(store)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="externalLoginService"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeUserManager Create(
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
IUserService userService,
|
||||
IExternalLoginService externalLoginService,
|
||||
MembershipProviderBase membershipProvider)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
|
||||
|
||||
var manager = new BackOfficeUserManager(new BackOfficeUserStore(userService, externalLoginService, membershipProvider));
|
||||
|
||||
return InitUserManager(manager, membershipProvider, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="customUserStore"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeUserManager Create(
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
BackOfficeUserStore customUserStore,
|
||||
MembershipProviderBase membershipProvider)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
if (customUserStore == null) throw new ArgumentNullException("customUserStore");
|
||||
|
||||
var manager = new BackOfficeUserManager(customUserStore);
|
||||
|
||||
return InitUserManager(manager, membershipProvider, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the user manager with the correct options
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
private static BackOfficeUserManager InitUserManager(BackOfficeUserManager manager, MembershipProviderBase membershipProvider, IdentityFactoryOptions<BackOfficeUserManager> options)
|
||||
{
|
||||
// Configure validation logic for usernames
|
||||
manager.UserValidator = new UserValidator<BackOfficeIdentityUser, int>(manager)
|
||||
{
|
||||
AllowOnlyAlphanumericUserNames = false,
|
||||
RequireUniqueEmail = true
|
||||
};
|
||||
|
||||
// Configure validation logic for passwords
|
||||
manager.PasswordValidator = new PasswordValidator
|
||||
{
|
||||
RequiredLength = membershipProvider.MinRequiredPasswordLength,
|
||||
RequireNonLetterOrDigit = membershipProvider.MinRequiredNonAlphanumericCharacters > 0,
|
||||
RequireDigit = false,
|
||||
RequireLowercase = false,
|
||||
RequireUppercase = false
|
||||
};
|
||||
|
||||
//use a custom hasher based on our membership provider
|
||||
manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider);
|
||||
|
||||
var dataProtectionProvider = options.DataProtectionProvider;
|
||||
if (dataProtectionProvider != null)
|
||||
{
|
||||
manager.UserTokenProvider = new DataProtectorTokenProvider<BackOfficeIdentityUser, int>(dataProtectionProvider.Create("ASP.NET Identity"));
|
||||
}
|
||||
|
||||
//custom identity factory for creating the identity object for which we auth against in the back office
|
||||
manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory();
|
||||
|
||||
//NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it
|
||||
|
||||
//// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
|
||||
//// You can write your own provider and plug in here.
|
||||
//manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
|
||||
//{
|
||||
// MessageFormat = "Your security code is: {0}"
|
||||
//});
|
||||
//manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
|
||||
//{
|
||||
// Subject = "Security Code",
|
||||
// BodyFormat = "Your security code is: {0}"
|
||||
//});
|
||||
|
||||
//manager.EmailService = new EmailService();
|
||||
//manager.SmsService = new SmsService();
|
||||
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic Back office user manager
|
||||
/// </summary>
|
||||
public class BackOfficeUserManager<T> : UserManager<T, int>
|
||||
where T : BackOfficeIdentityUser
|
||||
{
|
||||
public BackOfficeUserManager(IUserStore<T, int> store)
|
||||
: base(store)
|
||||
{
|
||||
}
|
||||
|
||||
#region What we support do not currently
|
||||
|
||||
//NOTE: Not sure if we really want/need to ever support this
|
||||
public override bool SupportsUserClaim
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
//TODO: Support this
|
||||
public override bool SupportsQueryableUsers
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
//TODO: Support this
|
||||
public override bool SupportsUserLockout
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
//TODO: Support this
|
||||
public override bool SupportsUserTwoFactor
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
//TODO: Support this
|
||||
public override bool SupportsUserPhoneNumber
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
588
src/Umbraco.Core/Security/BackOfficeUserStore.cs
Normal file
588
src/Umbraco.Core/Security/BackOfficeUserStore.cs
Normal file
@@ -0,0 +1,588 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeUserStore : DisposableObject,
|
||||
IUserStore<BackOfficeIdentityUser, int>,
|
||||
IUserPasswordStore<BackOfficeIdentityUser, int>,
|
||||
IUserEmailStore<BackOfficeIdentityUser, int>,
|
||||
IUserLoginStore<BackOfficeIdentityUser, int>,
|
||||
IUserRoleStore<BackOfficeIdentityUser, int>,
|
||||
IUserSecurityStampStore<BackOfficeIdentityUser, int>
|
||||
|
||||
//TODO: This would require additional columns/tables for now people will need to implement this on their own
|
||||
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
|
||||
//IUserTwoFactorStore<BackOfficeIdentityUser, int>,
|
||||
|
||||
//TODO: This will require additional columns/tables
|
||||
//IUserLockoutStore<BackOfficeIdentityUser, int>
|
||||
|
||||
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
|
||||
//IQueryableUserStore<BackOfficeIdentityUser, int>
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IExternalLoginService _externalLoginService;
|
||||
private bool _disposed = false;
|
||||
|
||||
public BackOfficeUserStore(IUserService userService, IExternalLoginService externalLoginService, MembershipProviderBase usersMembershipProvider)
|
||||
{
|
||||
_userService = userService;
|
||||
_externalLoginService = externalLoginService;
|
||||
if (userService == null) throw new ArgumentNullException("userService");
|
||||
if (usersMembershipProvider == null) throw new ArgumentNullException("usersMembershipProvider");
|
||||
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
|
||||
|
||||
_userService = userService;
|
||||
_externalLoginService = externalLoginService;
|
||||
|
||||
if (usersMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot use ASP.Net Identity with UmbracoMembersUserStore when the password format is not Hashed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task CreateAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
var userType = _userService.GetUserTypeByAlias(
|
||||
user.UserTypeAlias.IsNullOrWhiteSpace() ? _userService.GetDefaultMemberType() : user.UserTypeAlias);
|
||||
|
||||
var member = new User(userType)
|
||||
{
|
||||
DefaultToLiveEditing = false,
|
||||
Email = user.Email,
|
||||
Language = Configuration.GlobalSettings.DefaultUILanguage,
|
||||
Name = user.Name,
|
||||
Username = user.UserName,
|
||||
StartContentId = -1,
|
||||
StartMediaId = -1,
|
||||
IsLockedOut = false,
|
||||
IsApproved = true
|
||||
};
|
||||
|
||||
UpdateMemberProperties(member, user);
|
||||
|
||||
//the password must be 'something' it could be empty if authenticating
|
||||
// with an external provider so we'll just generate one and prefix it, the
|
||||
// prefix will help us determine if the password hasn't actually been specified yet.
|
||||
if (member.RawPasswordValue.IsNullOrWhiteSpace())
|
||||
{
|
||||
//this will hash the guid with a salt so should be nicely random
|
||||
var aspHasher = new PasswordHasher();
|
||||
member.RawPasswordValue = "___UIDEMPTYPWORD__" +
|
||||
aspHasher.HashPassword(Guid.NewGuid().ToString("N"));
|
||||
|
||||
}
|
||||
_userService.Save(member);
|
||||
|
||||
//re-assign id
|
||||
user.Id = member.Id;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public async Task UpdateAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
var asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
var found = _userService.GetUserById(asInt.Result);
|
||||
if (found != null)
|
||||
{
|
||||
if (UpdateMemberProperties(found, user))
|
||||
{
|
||||
_userService.Save(found);
|
||||
}
|
||||
|
||||
if (user.LoginsChanged)
|
||||
{
|
||||
var logins = await GetLoginsAsync(user);
|
||||
_externalLoginService.SaveUserLogins(found.Id, logins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task DeleteAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
var asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
var found = _userService.GetUserById(asInt.Result);
|
||||
if (found != null)
|
||||
{
|
||||
_userService.Delete(found);
|
||||
}
|
||||
_externalLoginService.DeleteUserLogins(asInt.Result);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a user
|
||||
/// </summary>
|
||||
/// <param name="userId"/>
|
||||
/// <returns/>
|
||||
public Task<BackOfficeIdentityUser> FindByIdAsync(int userId)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var user = _userService.GetUserById(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Task.FromResult(AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a user by name
|
||||
/// </summary>
|
||||
/// <param name="userName"/>
|
||||
/// <returns/>
|
||||
public Task<BackOfficeIdentityUser> FindByNameAsync(string userName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var user = _userService.GetByUsername(userName);
|
||||
if (user == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = AssignLoginsCallback(Mapper.Map<BackOfficeIdentityUser>(user));
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="passwordHash"/>
|
||||
/// <returns/>
|
||||
public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
if (passwordHash.IsNullOrWhiteSpace()) throw new ArgumentNullException("passwordHash");
|
||||
|
||||
user.PasswordHash = passwordHash;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user password hash
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<string> GetPasswordHashAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
return Task.FromResult(user.PasswordHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a user has a password set
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<bool> HasPasswordAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
return Task.FromResult(user.PasswordHash.IsNullOrWhiteSpace() == false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the user email
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="email"/>
|
||||
/// <returns/>
|
||||
public Task SetEmailAsync(BackOfficeIdentityUser user, string email)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException("email");
|
||||
|
||||
user.Email = email;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user email
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<string> GetEmailAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
return Task.FromResult(user.Email);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the user email is confirmed
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<bool> GetEmailConfirmedAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the user email is confirmed
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="confirmed"/>
|
||||
/// <returns/>
|
||||
public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the user associated with this email
|
||||
/// </summary>
|
||||
/// <param name="email"/>
|
||||
/// <returns/>
|
||||
public Task<BackOfficeIdentityUser> FindByEmailAsync(string email)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var user = _userService.GetByEmail(email);
|
||||
var result = user == null
|
||||
? null
|
||||
: Mapper.Map<BackOfficeIdentityUser>(user);
|
||||
|
||||
return Task.FromResult(AssignLoginsCallback(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user login with the specified provider and key
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="login"/>
|
||||
/// <returns/>
|
||||
public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
if (login == null) throw new ArgumentNullException("login");
|
||||
|
||||
var logins = user.Logins;
|
||||
var instance = new IdentityUserLogin(login.LoginProvider, login.ProviderKey, user.Id);
|
||||
var userLogin = instance;
|
||||
logins.Add(userLogin);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the user login with the specified combination if it exists
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="login"/>
|
||||
/// <returns/>
|
||||
public Task RemoveLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
if (login == null) throw new ArgumentNullException("login");
|
||||
|
||||
var provider = login.LoginProvider;
|
||||
var key = login.ProviderKey;
|
||||
var userLogin = user.Logins.SingleOrDefault((l => l.LoginProvider == provider && l.ProviderKey == key));
|
||||
if (userLogin != null)
|
||||
user.Logins.Remove(userLogin);
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the linked accounts for this user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<IList<UserLoginInfo>> GetLoginsAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
return Task.FromResult((IList<UserLoginInfo>)
|
||||
user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey)).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the user associated with this login
|
||||
/// </summary>
|
||||
/// <returns/>
|
||||
public Task<BackOfficeIdentityUser> FindAsync(UserLoginInfo login)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (login == null) throw new ArgumentNullException("login");
|
||||
|
||||
//get all logins associated with the login id
|
||||
var result = _externalLoginService.Find(login).ToArray();
|
||||
if (result.Any())
|
||||
{
|
||||
//return the first member that matches the result
|
||||
var output = (from l in result
|
||||
select _userService.GetUserById(l.UserId)
|
||||
into user
|
||||
where user != null
|
||||
select Mapper.Map<BackOfficeIdentityUser>(user)).FirstOrDefault();
|
||||
|
||||
return Task.FromResult(AssignLoginsCallback(output));
|
||||
}
|
||||
|
||||
return Task.FromResult<BackOfficeIdentityUser>(null);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a user to a role (section)
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="roleName"/>
|
||||
/// <returns/>
|
||||
public Task AddToRoleAsync(BackOfficeIdentityUser user, string roleName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
if (user.AllowedSections.InvariantContains(roleName)) return Task.FromResult(0);
|
||||
|
||||
var asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
var found = _userService.GetUserById(asInt.Result);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
found.AddAllowedSection(roleName);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the role (allowed section) for the user
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="roleName"/>
|
||||
/// <returns/>
|
||||
public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string roleName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
if (user.AllowedSections.InvariantContains(roleName) == false) return Task.FromResult(0);
|
||||
|
||||
var asInt = user.Id.TryConvertTo<int>();
|
||||
if (asInt == false)
|
||||
{
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
var found = _userService.GetUserById(asInt.Result);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
found.RemoveAllowedSection(roleName);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the roles for this user
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<IList<string>> GetRolesAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
return Task.FromResult((IList<string>)user.AllowedSections.ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a user is in the role
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="roleName"/>
|
||||
/// <returns/>
|
||||
public Task<bool> IsInRoleAsync(BackOfficeIdentityUser user, string roleName)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
return Task.FromResult(user.AllowedSections.InvariantContains(roleName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the security stamp for the user
|
||||
/// </summary>
|
||||
/// <param name="user"/><param name="stamp"/>
|
||||
/// <returns/>
|
||||
public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
user.SecurityStamp = stamp;
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user security stamp
|
||||
/// </summary>
|
||||
/// <param name="user"/>
|
||||
/// <returns/>
|
||||
public Task<string> GetSecurityStampAsync(BackOfficeIdentityUser user)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (user == null) throw new ArgumentNullException("user");
|
||||
|
||||
//the stamp cannot be null, so if it is currently null then we'll just return a hash of the password
|
||||
return Task.FromResult(user.SecurityStamp.IsNullOrWhiteSpace()
|
||||
? user.PasswordHash.ToMd5()
|
||||
: user.SecurityStamp);
|
||||
}
|
||||
|
||||
private BackOfficeIdentityUser AssignLoginsCallback(BackOfficeIdentityUser user)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
user.SetLoginsCallback(new Lazy<IEnumerable<IIdentityUserLogin>>(() =>
|
||||
_externalLoginService.GetAll(user.Id)));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private bool UpdateMemberProperties(Models.Membership.IUser user, BackOfficeIdentityUser identityUser)
|
||||
{
|
||||
var anythingChanged = false;
|
||||
//don't assign anything if nothing has changed as this will trigger
|
||||
//the track changes of the model
|
||||
if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.Name = identityUser.Name;
|
||||
}
|
||||
if (user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.Email = identityUser.Email;
|
||||
}
|
||||
if (user.FailedPasswordAttempts != identityUser.AccessFailedCount)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.FailedPasswordAttempts = identityUser.AccessFailedCount;
|
||||
}
|
||||
if (user.IsLockedOut != identityUser.LockoutEnabled)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.IsLockedOut = identityUser.LockoutEnabled;
|
||||
}
|
||||
if (user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.Username = identityUser.UserName;
|
||||
}
|
||||
if (user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.RawPasswordValue = identityUser.PasswordHash;
|
||||
}
|
||||
|
||||
if (user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.Language = identityUser.Culture;
|
||||
}
|
||||
if (user.StartMediaId != identityUser.StartMediaId)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.StartMediaId = identityUser.StartMediaId;
|
||||
}
|
||||
if (user.StartContentId != identityUser.StartContentId)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.StartContentId = identityUser.StartContentId;
|
||||
}
|
||||
if (user.SecurityStamp != identityUser.SecurityStamp)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.SecurityStamp = identityUser.SecurityStamp;
|
||||
}
|
||||
if (user.AllowedSections.ContainsAll(identityUser.AllowedSections) == false
|
||||
|| identityUser.AllowedSections.ContainsAll(user.AllowedSections) == false)
|
||||
{
|
||||
anythingChanged = true;
|
||||
foreach (var allowedSection in user.AllowedSections)
|
||||
{
|
||||
user.RemoveAllowedSection(allowedSection);
|
||||
}
|
||||
foreach (var allowedApplication in identityUser.AllowedSections)
|
||||
{
|
||||
user.AddAllowedSection(allowedApplication);
|
||||
}
|
||||
}
|
||||
|
||||
return anythingChanged;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Umbraco.Core/Security/MembershipPasswordHasher.cs
Normal file
31
src/Umbraco.Core/Security/MembershipPasswordHasher.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNet.Identity;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom password hasher that conforms to the current password hashing done in Umbraco
|
||||
/// </summary>
|
||||
internal class MembershipPasswordHasher : IPasswordHasher
|
||||
{
|
||||
private readonly MembershipProviderBase _provider;
|
||||
|
||||
public MembershipPasswordHasher(MembershipProviderBase provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
return _provider.HashPasswordForStorage(password);
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
|
||||
{
|
||||
return _provider.VerifyPassword(providedPassword, hashedPassword)
|
||||
? PasswordVerificationResult.Success
|
||||
: PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,19 @@ namespace Umbraco.Core.Security
|
||||
/// </summary>
|
||||
public abstract class MembershipProviderBase : MembershipProvider
|
||||
{
|
||||
|
||||
public string HashPasswordForStorage(string password)
|
||||
{
|
||||
string salt;
|
||||
var hashed = EncryptOrHashNewPassword(password, out salt);
|
||||
return FormatPasswordForStorage(hashed, salt);
|
||||
}
|
||||
|
||||
public bool VerifyPassword(string password, string hashedPassword)
|
||||
{
|
||||
return CheckPassword(password, hashedPassword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Providers can override this setting, default is 7
|
||||
/// </summary>
|
||||
|
||||
@@ -13,21 +13,21 @@ using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
internal static class MembershipProviderExtensions
|
||||
public static class MembershipProviderExtensions
|
||||
{
|
||||
public static MembershipUserCollection FindUsersByName(this MembershipProvider provider, string usernameToMatch)
|
||||
internal static MembershipUserCollection FindUsersByName(this MembershipProvider provider, string usernameToMatch)
|
||||
{
|
||||
int totalRecords = 0;
|
||||
return provider.FindUsersByName(usernameToMatch, 0, int.MaxValue, out totalRecords);
|
||||
}
|
||||
|
||||
public static MembershipUserCollection FindUsersByEmail(this MembershipProvider provider, string emailToMatch)
|
||||
internal static MembershipUserCollection FindUsersByEmail(this MembershipProvider provider, string emailToMatch)
|
||||
{
|
||||
int totalRecords = 0;
|
||||
return provider.FindUsersByEmail(emailToMatch, 0, int.MaxValue, out totalRecords);
|
||||
}
|
||||
|
||||
public static MembershipUser CreateUser(this MembershipProvider provider, string username, string password, string email)
|
||||
internal static MembershipUser CreateUser(this MembershipProvider provider, string username, string password, string email)
|
||||
{
|
||||
MembershipCreateStatus status;
|
||||
var user = provider.CreateUser(username, password, email, null, null, true, null, out status);
|
||||
@@ -80,7 +80,7 @@ namespace Umbraco.Core.Security
|
||||
/// </summary>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
|
||||
internal static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
|
||||
{
|
||||
var username = membershipProvider.GetCurrentUserName();
|
||||
return username.IsNullOrWhiteSpace()
|
||||
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Security
|
||||
/// </summary>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetCurrentUserName(this MembershipProvider membershipProvider)
|
||||
internal static string GetCurrentUserName(this MembershipProvider membershipProvider)
|
||||
{
|
||||
if (HostingEnvironment.IsHosted)
|
||||
{
|
||||
|
||||
@@ -1,115 +1,190 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A custom user identity for the Umbraco backoffice
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All values are lazy loaded for performance reasons as the constructor is called for every single request
|
||||
/// This inherits from FormsIdentity for backwards compatibility reasons since we still support the forms auth cookie, in v8 we can
|
||||
/// change over to 'pure' asp.net identity and just inherit from ClaimsIdentity.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class UmbracoBackOfficeIdentity : FormsIdentity
|
||||
{
|
||||
public UmbracoBackOfficeIdentity(FormsAuthenticationTicket ticket)
|
||||
/// <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");
|
||||
|
||||
_currentIssuer = claimsIdentity.AuthenticationType;
|
||||
UserData = userdata;
|
||||
AddClaims(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)
|
||||
{
|
||||
UserData = ticket.UserData;
|
||||
EnsureDeserialized();
|
||||
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;
|
||||
AddClaims(identity);
|
||||
Actor = identity.Clone();
|
||||
}
|
||||
|
||||
UserData = identity.UserData;
|
||||
AddUserDataClaims();
|
||||
}
|
||||
|
||||
public const string Issuer = "UmbracoBackOffice";
|
||||
private readonly string _currentIssuer = Issuer;
|
||||
|
||||
private void AddClaims(ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
foreach (var claim in claimsIdentity.Claims)
|
||||
{
|
||||
AddClaim(claim);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds claims based on the UserData data
|
||||
/// </summary>
|
||||
private void AddUserDataClaims()
|
||||
{
|
||||
AddClaims(new[]
|
||||
{
|
||||
//This is the id that 'identity' uses to check for the user id
|
||||
new Claim(ClaimTypes.NameIdentifier, Id.ToString(), null, Issuer, Issuer, this),
|
||||
|
||||
new Claim(Constants.Security.StartContentNodeIdClaimType, StartContentNode.ToInvariantString(), null, Issuer, Issuer, this),
|
||||
new Claim(Constants.Security.StartMediaNodeIdClaimType, StartMediaNode.ToInvariantString(), null, Issuer, Issuer, this),
|
||||
new Claim(Constants.Security.AllowedApplicationsClaimType, string.Join(",", AllowedApplications), null, Issuer, Issuer, this),
|
||||
|
||||
//TODO: Similar one created by the ClaimsIdentityFactory<TUser, TKey> not sure we need this
|
||||
new Claim(Constants.Security.CultureClaimType, Culture, null, Issuer, Issuer, this)
|
||||
|
||||
//TODO: Role claims are added by the default ClaimsIdentityFactory<TUser, TKey> based on the result from
|
||||
// the user manager manager.GetRolesAsync method so not sure if we can do that there or needs to be done here
|
||||
// and each role should be a different claim, not a single string
|
||||
|
||||
//new Claim(ClaimTypes.Role, string.Join(",", Roles), null, Issuer, Issuer, this)
|
||||
});
|
||||
|
||||
//TODO: Find out why sessionid is null - this depends on how the identity is created!
|
||||
if (SessionId.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
AddClaim(new Claim(Constants.Security.SessionIdClaimType, SessionId, null, Issuer, Issuer, this));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected internal UserData UserData { get; private set; }
|
||||
|
||||
/// <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; }
|
||||
}
|
||||
|
||||
protected readonly string UserData;
|
||||
internal UserData DeserializedData;
|
||||
|
||||
public int StartContentNode
|
||||
{
|
||||
get
|
||||
{
|
||||
return DeserializedData.StartContentNode;
|
||||
}
|
||||
get { return UserData.StartContentNode; }
|
||||
}
|
||||
|
||||
public int StartMediaNode
|
||||
{
|
||||
get { return DeserializedData.StartMediaNode; }
|
||||
get { return UserData.StartMediaNode; }
|
||||
}
|
||||
|
||||
public string[] AllowedApplications
|
||||
{
|
||||
get { return DeserializedData.AllowedApplications; }
|
||||
get { return UserData.AllowedApplications; }
|
||||
}
|
||||
|
||||
|
||||
public object Id
|
||||
{
|
||||
get { return DeserializedData.Id; }
|
||||
get { return UserData.Id; }
|
||||
}
|
||||
|
||||
public string RealName
|
||||
{
|
||||
get { return DeserializedData.RealName; }
|
||||
get { return UserData.RealName; }
|
||||
}
|
||||
|
||||
public string Culture
|
||||
{
|
||||
get { return DeserializedData.Culture; }
|
||||
get { return UserData.Culture; }
|
||||
}
|
||||
|
||||
public string SessionId
|
||||
{
|
||||
get { return DeserializedData.SessionId; }
|
||||
get { return UserData.SessionId; }
|
||||
}
|
||||
|
||||
//public int SessionTimeout
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// EnsureDeserialized();
|
||||
// return DeserializedData.SessionTimeout;
|
||||
// }
|
||||
//}
|
||||
|
||||
public string[] Roles
|
||||
{
|
||||
get { return DeserializedData.Roles; }
|
||||
get { return UserData.Roles; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will ensure we only deserialize once
|
||||
/// Gets a copy of the current <see cref="T:UmbracoBackOfficeIdentity"/> instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For performance reasons, we'll also check if there's an http context available,
|
||||
/// if so, we'll chuck our instance in there so that we only deserialize once per request.
|
||||
/// </remarks>
|
||||
protected void EnsureDeserialized()
|
||||
/// <returns>
|
||||
/// A copy of the current <see cref="T:UmbracoBackOfficeIdentity"/> instance.
|
||||
/// </returns>
|
||||
public override ClaimsIdentity Clone()
|
||||
{
|
||||
if (DeserializedData != null)
|
||||
return;
|
||||
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
//check if we've already done this in this request
|
||||
var data = HttpContext.Current.Items[typeof(UmbracoBackOfficeIdentity)] as UserData;
|
||||
if (data != null)
|
||||
{
|
||||
DeserializedData = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(UserData))
|
||||
{
|
||||
throw new NullReferenceException("The " + typeof(UserData) + " found in the ticket cannot be empty");
|
||||
}
|
||||
DeserializedData = JsonConvert.DeserializeObject<UserData>(UserData);
|
||||
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
HttpContext.Current.Items[typeof (UmbracoBackOfficeIdentity)] = DeserializedData;
|
||||
}
|
||||
return new UmbracoBackOfficeIdentity(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Umbraco.Core.Security
|
||||
/// </summary>
|
||||
public abstract class UmbracoMembershipProviderBase : MembershipProviderBase
|
||||
{
|
||||
|
||||
|
||||
public abstract string DefaultMemberTypeAlias { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
78
src/Umbraco.Core/Services/ExternalLoginService.cs
Normal file
78
src/Umbraco.Core/Services/ExternalLoginService.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
public class ExternalLoginService : RepositoryService, IExternalLoginService
|
||||
{
|
||||
public ExternalLoginService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger)
|
||||
: base(provider, repositoryFactory, logger)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all user logins assigned
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IIdentityUserLogin> GetAll(int userId)
|
||||
{
|
||||
using (var repo = RepositoryFactory.CreateExternalLoginRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repo.GetByQuery(new Query<IIdentityUserLogin>().Where(x => x.UserId == userId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all logins matching the login info - generally there should only be one but in some cases
|
||||
/// there might be more than one depending on if an adminstrator has been editing/removing members
|
||||
/// </summary>
|
||||
/// <param name="login"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IIdentityUserLogin> Find(UserLoginInfo login)
|
||||
{
|
||||
using (var repo = RepositoryFactory.CreateExternalLoginRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
return repo.GetByQuery(new Query<IIdentityUserLogin>()
|
||||
.Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save user logins
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="logins"></param>
|
||||
public void SaveUserLogins(int userId, IEnumerable<UserLoginInfo> logins)
|
||||
{
|
||||
var uow = UowProvider.GetUnitOfWork();
|
||||
using (var repo = RepositoryFactory.CreateExternalLoginRepository(uow))
|
||||
{
|
||||
repo.SaveUserLogins(userId, logins);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all user logins - normally used when a member is deleted
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
public void DeleteUserLogins(int userId)
|
||||
{
|
||||
var uow = UowProvider.GetUnitOfWork();
|
||||
using (var repo = RepositoryFactory.CreateExternalLoginRepository(uow))
|
||||
{
|
||||
repo.DeleteUserLogins(userId);
|
||||
uow.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
40
src/Umbraco.Core/Services/IExternalLoginService.cs
Normal file
40
src/Umbraco.Core/Services/IExternalLoginService.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to store the external login info, this can be replaced with your own implementation
|
||||
/// </summary>
|
||||
public interface IExternalLoginService : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all user logins assigned
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IIdentityUserLogin> GetAll(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all logins matching the login info - generally there should only be one but in some cases
|
||||
/// there might be more than one depending on if an adminstrator has been editing/removing members
|
||||
/// </summary>
|
||||
/// <param name="login"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IIdentityUserLogin> Find(UserLoginInfo login);
|
||||
|
||||
/// <summary>
|
||||
/// Save user logins
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="logins"></param>
|
||||
void SaveUserLogins(int userId, IEnumerable<UserLoginInfo> logins);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all user logins - normally used when a member is deleted
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
void DeleteUserLogins(int userId);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Umbraco.Core.Services
|
||||
private Lazy<IMemberTypeService> _memberTypeService;
|
||||
private Lazy<IMemberGroupService> _memberGroupService;
|
||||
private Lazy<INotificationService> _notificationService;
|
||||
private Lazy<IExternalLoginService> _externalLoginService;
|
||||
|
||||
/// <summary>
|
||||
/// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used
|
||||
@@ -91,8 +92,10 @@ namespace Umbraco.Core.Services
|
||||
IDomainService domainService = null,
|
||||
ITaskService taskService = null,
|
||||
IMacroService macroService = null,
|
||||
IPublicAccessService publicAccessService = null)
|
||||
IPublicAccessService publicAccessService = null,
|
||||
IExternalLoginService externalLoginService = null)
|
||||
{
|
||||
if (externalLoginService != null) _externalLoginService = new Lazy<IExternalLoginService>(() => externalLoginService);
|
||||
if (auditService != null) _auditService = new Lazy<IAuditService>(() => auditService);
|
||||
if (localizedTextService != null) _localizedTextService = new Lazy<ILocalizedTextService>(() => localizedTextService);
|
||||
if (tagService != null) _tagService = new Lazy<ITagService>(() => tagService);
|
||||
@@ -145,6 +148,9 @@ namespace Umbraco.Core.Services
|
||||
var provider = dbUnitOfWorkProvider;
|
||||
var fileProvider = fileUnitOfWorkProvider;
|
||||
|
||||
if (_externalLoginService == null)
|
||||
_externalLoginService = new Lazy<IExternalLoginService>(() => new ExternalLoginService(provider, repositoryFactory, logger));
|
||||
|
||||
if (_publicAccessService == null)
|
||||
_publicAccessService = new Lazy<IPublicAccessService>(() => new PublicAccessService(provider, repositoryFactory, logger));
|
||||
|
||||
@@ -415,5 +421,9 @@ namespace Umbraco.Core.Services
|
||||
get { return _memberGroupService.Value; }
|
||||
}
|
||||
|
||||
public IExternalLoginService ExternalLoginService
|
||||
{
|
||||
get { return _externalLoginService.Value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,30 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security">
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.Cookies, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
|
||||
@@ -70,6 +94,9 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Configuration" />
|
||||
@@ -320,6 +347,13 @@
|
||||
<Compile Include="IDisposeOnRequestEnd.cs" />
|
||||
<Compile Include="Models\AuditItem.cs" />
|
||||
<Compile Include="Models\AuditType.cs" />
|
||||
<Compile Include="Models\Identity\IdentityModelMappings.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUser.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUserClaim.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUserLogin.cs" />
|
||||
<Compile Include="Models\Identity\IdentityUserRole.cs" />
|
||||
<Compile Include="Models\Identity\BackOfficeIdentityUser.cs" />
|
||||
<Compile Include="Models\Identity\IIdentityUserLogin.cs" />
|
||||
<Compile Include="Models\PublicAccessEntry.cs" />
|
||||
<Compile Include="Models\PublicAccessRule.cs" />
|
||||
<Compile Include="Models\Rdbms\AccessDto.cs" />
|
||||
@@ -337,15 +371,19 @@
|
||||
<Compile Include="Media\Exif\ExifTagFactory.cs" />
|
||||
<Compile Include="Models\Rdbms\AccessRuleDto.cs" />
|
||||
<Compile Include="Models\Rdbms\DocumentPublishedReadOnlyDto.cs" />
|
||||
<Compile Include="Models\Rdbms\ExternalLoginDto.cs" />
|
||||
<Compile Include="Models\UmbracoDomain.cs" />
|
||||
<Compile Include="Models\DoNotCloneAttribute.cs" />
|
||||
<Compile Include="Models\IDomain.cs" />
|
||||
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\PublicAccessEntryFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\TaskFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\TaskTypeFactory.cs" />
|
||||
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
|
||||
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddExternalLoginsTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddPublicAccessTables.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\AddUserSecurityStampColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MovePublicAccessXmlDataToDb.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveHelpTextColumn.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\RemoveLanguageLocaleColumn.cs" />
|
||||
@@ -372,8 +410,10 @@
|
||||
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
|
||||
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\IAuditRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\IDomainRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\IExternalLoginRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\IPublicAccessRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\ITaskRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\ITaskTypeRepository.cs" />
|
||||
@@ -381,6 +421,10 @@
|
||||
<Compile Include="Persistence\Repositories\RepositoryCacheOptions.cs" />
|
||||
<Compile Include="Persistence\Repositories\TaskRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\TaskTypeRepository.cs" />
|
||||
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
|
||||
<Compile Include="Security\BackOfficeUserManager.cs" />
|
||||
<Compile Include="Security\BackOfficeUserStore.cs" />
|
||||
<Compile Include="Security\MembershipPasswordHasher.cs" />
|
||||
<Compile Include="ServiceProviderExtensions.cs" />
|
||||
<Compile Include="IO\ResizedImage.cs" />
|
||||
<Compile Include="IO\UmbracoMediaFile.cs" />
|
||||
@@ -391,8 +435,10 @@
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeZero\MigrateStylesheetDataToFile.cs" />
|
||||
<Compile Include="Services\AuditService.cs" />
|
||||
<Compile Include="Services\DomainService.cs" />
|
||||
<Compile Include="Services\ExternalLoginService.cs" />
|
||||
<Compile Include="Services\IAuditService.cs" />
|
||||
<Compile Include="Services\IDomainService.cs" />
|
||||
<Compile Include="Services\IExternalLoginService.cs" />
|
||||
<Compile Include="Services\IPublicAccessService.cs" />
|
||||
<Compile Include="Services\ITaskService.cs" />
|
||||
<Compile Include="Services\PublicAccessService.cs" />
|
||||
@@ -1102,7 +1148,7 @@
|
||||
<Compile Include="Security\MembershipProviderExtensions.cs" />
|
||||
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
|
||||
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
|
||||
<Compile Include="Security\UmbracoMembersMembershipProviderBase.cs" />
|
||||
<Compile Include="Security\IUmbracoMemberTypeMembershipProvider.cs" />
|
||||
<Compile Include="Security\UserData.cs" />
|
||||
<Compile Include="Serialization\AbstractSerializationService.cs" />
|
||||
<Compile Include="Serialization\Formatter.cs" />
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -3,6 +3,8 @@
|
||||
<package id="AutoMapper" version="3.0.0" targetFramework="net45" />
|
||||
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net45" />
|
||||
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Identity.Core" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.WebApi.Client" version="4.0.30506.0" targetFramework="net45" />
|
||||
@@ -10,10 +12,15 @@
|
||||
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.28" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.Cookies" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
|
||||
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
|
||||
<package id="MySql.Data" version="6.6.5" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
|
||||
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -152,6 +152,38 @@
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
</assemblyBinding>
|
||||
|
||||
</runtime>
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
"typeahead.js": "~0.10.5",
|
||||
"underscore": "~1.7.0",
|
||||
"rgrove-lazyload": "*",
|
||||
"bootstrap-social": "~4.8.0",
|
||||
"jquery": "2.0.3",
|
||||
"jquery-file-upload": "~9.4.0",
|
||||
"jquery-ui": "1.10.3",
|
||||
"angular-dynamic-locale": "~0.1.27"
|
||||
"angular-dynamic-locale": "~0.1.27"
|
||||
},
|
||||
"exportsOverride": {
|
||||
"rgrove-lazyload": {
|
||||
@@ -37,8 +38,18 @@
|
||||
"angular-dynamic-locale": {
|
||||
"": "tmhDynamicLocale.min.{js,js.map}"
|
||||
},
|
||||
"bootstrap-social": {
|
||||
"": "bootstrap-social.css"
|
||||
},
|
||||
"font-awesome": {
|
||||
"css": "css/font-awesome.min.css",
|
||||
"fonts": "fonts/*"
|
||||
},
|
||||
"bootstrap": {
|
||||
"ignore": "*.ignore"
|
||||
},
|
||||
"jquery": {
|
||||
"": "jquery.min.{js,map}"
|
||||
"": "jquery.min.{js,map}"
|
||||
},
|
||||
"jquery-file-upload": {
|
||||
"": "**/jquery.{fileupload,fileupload-process,fileupload-angular,fileupload-image}.js"
|
||||
@@ -53,7 +64,7 @@
|
||||
"blueimp-tmpl": {
|
||||
"ignore": "*.ignore"
|
||||
},
|
||||
|
||||
|
||||
"blueimp-canvas-to-blob": {
|
||||
"ignore": "*.ignore"
|
||||
}
|
||||
|
||||
@@ -10,4 +10,12 @@ var app = angular.module('umbraco', [
|
||||
'blueimp.fileupload',
|
||||
'tmh.dynamicLocale'
|
||||
]);
|
||||
var packages = angular.module("umbraco.packages", []);
|
||||
var packages = angular.module("umbraco.packages", []);
|
||||
|
||||
//Call a document callback if defined, this is sort of a dodgy hack to
|
||||
// be able to configure angular values in the Default.cshtml
|
||||
// view which is much easier to do that configuring values by injecting them in the back office controller
|
||||
// to follow through to the js initialization stuff
|
||||
if (angular.isFunction(document.angularReady)) {
|
||||
document.angularReady.apply(this, [app]);
|
||||
}
|
||||
@@ -52,6 +52,24 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
|
||||
'Login failed for user ' + username);
|
||||
},
|
||||
|
||||
unlinkLogin: function (loginProvider, providerKey) {
|
||||
if (!loginProvider || !providerKey) {
|
||||
return angularHelper.rejectedPromise({
|
||||
errorMsg: 'loginProvider or providerKey cannot be empty'
|
||||
});
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"PostUnLinkLogin"), {
|
||||
loginProvider: loginProvider,
|
||||
providerKey: providerKey
|
||||
}),
|
||||
'Unlinking login provider failed');
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.authResource#performLogout
|
||||
@@ -105,6 +123,16 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
|
||||
"GetCurrentUser")),
|
||||
'Server call failed for getting current user');
|
||||
},
|
||||
|
||||
getCurrentUserLinkedLogins: function () {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"authenticationApiBaseUrl",
|
||||
"GetCurrentUserLinkedLogins")),
|
||||
'Server call failed for getting current users linked logins');
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
|
||||
@@ -56,7 +56,7 @@ angular.module('umbraco.services')
|
||||
|
||||
/**
|
||||
Method to count down the current user's timeout seconds,
|
||||
this will continually count down their current remaining seconds every 2 seconds until
|
||||
this will continually count down their current remaining seconds every 5 seconds until
|
||||
there are no more seconds remaining.
|
||||
*/
|
||||
function countdownUserTimeout() {
|
||||
@@ -64,8 +64,8 @@ angular.module('umbraco.services')
|
||||
$timeout(function () {
|
||||
|
||||
if (currentUser) {
|
||||
//countdown by 2 seconds since that is how long our timer is for.
|
||||
currentUser.remainingAuthSeconds -= 2;
|
||||
//countdown by 5 seconds since that is how long our timer is for.
|
||||
currentUser.remainingAuthSeconds -= 5;
|
||||
|
||||
//if there are more than 30 remaining seconds, recurse!
|
||||
if (currentUser.remainingAuthSeconds > 30) {
|
||||
@@ -128,7 +128,7 @@ angular.module('umbraco.services')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 2000, //every 2 seconds
|
||||
}, 5000, //every 5 seconds
|
||||
false); //false = do NOT execute a digest for every iteration
|
||||
}
|
||||
|
||||
|
||||
@@ -2,46 +2,72 @@
|
||||
// -------------------------
|
||||
|
||||
.login-overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: @blackLight url(../img/application/logo.png) no-repeat 25px 30px fixed !important;
|
||||
background-size: 30px 30px !important;
|
||||
color: @white;
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
margin: 0 !Important;
|
||||
padding: 0;
|
||||
border-radius: 0
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: @blackLight url(../img/application/logo.png) no-repeat 25px 30px fixed !important;
|
||||
background-size: 30px 30px !important;
|
||||
color: @white;
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
margin: 0 !Important;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.login-overlay .umb-modalcolumn{
|
||||
background: none;
|
||||
border: none;
|
||||
.login-overlay .umb-modalcolumn {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.login-overlay .form {
|
||||
display: block;
|
||||
padding-top: 100px;
|
||||
padding-left: 165px;
|
||||
width: 370px;
|
||||
text-align: right
|
||||
display: block;
|
||||
padding-top: 100px;
|
||||
padding-left: 165px;
|
||||
width: 370px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.login-overlay h1 {
|
||||
display: block;
|
||||
text-align: right;
|
||||
color: @white;
|
||||
font-size: 18px;
|
||||
font-weight: normal
|
||||
display: block;
|
||||
text-align: right;
|
||||
color: @white;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.login-overlay .alert.alert-error{
|
||||
.login-overlay .alert.alert-error {
|
||||
display: inline-block;
|
||||
width: 270px;
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#hrOr {
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
#hrOr hr {
|
||||
margin: 0px;
|
||||
border: none;
|
||||
background-color: @gray;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#hrOr div {
|
||||
background-color: black;
|
||||
position: relative;
|
||||
top: -16px;
|
||||
border: 1px solid @gray;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: auto;
|
||||
color: @grayLight;
|
||||
}
|
||||
|
||||
@@ -280,4 +280,11 @@
|
||||
.umb-dialog a.text-success:hover,
|
||||
.umb-dialog a.text-success:focus,
|
||||
.umb-panel a.text-success:hover,
|
||||
.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); }
|
||||
.umb-panel a.text-success:focus { color: darken(@formSuccessText, 10%); }
|
||||
|
||||
.external-logins form {
|
||||
margin:0;
|
||||
}
|
||||
.external-logins button {
|
||||
margin:5px;
|
||||
}
|
||||
|
||||
@@ -1,61 +1,66 @@
|
||||
angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", function ($scope, localizationService, userService) {
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name signin
|
||||
* @methodOf MainController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* signs the user in
|
||||
*/
|
||||
var d = new Date();
|
||||
//var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
|
||||
localizationService.localize("login_greeting"+d.getDay()).then(function(label){
|
||||
$scope.greeting = label;
|
||||
}); // weekday[d.getDay()];
|
||||
|
||||
$scope.errorMsg = "";
|
||||
|
||||
$scope.loginSubmit = function (login, password) {
|
||||
|
||||
//if the login and password are not empty we need to automatically
|
||||
// validate them - this is because if there are validation errors on the server
|
||||
// then the user has to change both username & password to resubmit which isn't ideal,
|
||||
// so if they're not empty , we'l just make sure to set them to valid.
|
||||
if (login && password && login.length > 0 && password.length > 0) {
|
||||
$scope.loginForm.username.$setValidity('auth', true);
|
||||
$scope.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
|
||||
|
||||
if ($scope.loginForm.$invalid) {
|
||||
return;
|
||||
}
|
||||
angular.module("umbraco").controller("Umbraco.Dialogs.LoginController",
|
||||
function ($scope, localizationService, userService, externalLoginInfo) {
|
||||
|
||||
userService.authenticate(login, password)
|
||||
.then(function (data) {
|
||||
$scope.submit(true);
|
||||
}, function (reason) {
|
||||
$scope.errorMsg = reason.errorMsg;
|
||||
|
||||
//set the form inputs to invalid
|
||||
$scope.loginForm.username.$setValidity("auth", false);
|
||||
$scope.loginForm.password.$setValidity("auth", false);
|
||||
});
|
||||
|
||||
//setup a watch for both of the model values changing, if they change
|
||||
// while the form is invalid, then revalidate them so that the form can
|
||||
// be submitted again.
|
||||
$scope.loginForm.username.$viewChangeListeners.push(function () {
|
||||
if ($scope.loginForm.username.$invalid) {
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name signin
|
||||
* @methodOf MainController
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* signs the user in
|
||||
*/
|
||||
var d = new Date();
|
||||
//var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
|
||||
localizationService.localize("login_greeting" + d.getDay()).then(function (label) {
|
||||
$scope.greeting = label;
|
||||
}); // weekday[d.getDay()];
|
||||
|
||||
$scope.errorMsg = "";
|
||||
|
||||
$scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
|
||||
$scope.externalLoginProviders = externalLoginInfo.providers;
|
||||
$scope.externalLoginInfo = externalLoginInfo;
|
||||
|
||||
$scope.loginSubmit = function (login, password) {
|
||||
|
||||
//if the login and password are not empty we need to automatically
|
||||
// validate them - this is because if there are validation errors on the server
|
||||
// then the user has to change both username & password to resubmit which isn't ideal,
|
||||
// so if they're not empty , we'l just make sure to set them to valid.
|
||||
if (login && password && login.length > 0 && password.length > 0) {
|
||||
$scope.loginForm.username.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
$scope.loginForm.password.$viewChangeListeners.push(function () {
|
||||
if ($scope.loginForm.password.$invalid) {
|
||||
$scope.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
if ($scope.loginForm.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
userService.authenticate(login, password)
|
||||
.then(function (data) {
|
||||
$scope.submit(true);
|
||||
}, function (reason) {
|
||||
$scope.errorMsg = reason.errorMsg;
|
||||
|
||||
//set the form inputs to invalid
|
||||
$scope.loginForm.username.$setValidity("auth", false);
|
||||
$scope.loginForm.password.$setValidity("auth", false);
|
||||
});
|
||||
|
||||
//setup a watch for both of the model values changing, if they change
|
||||
// while the form is invalid, then revalidate them so that the form can
|
||||
// be submitted again.
|
||||
$scope.loginForm.username.$viewChangeListeners.push(function () {
|
||||
if ($scope.loginForm.username.$invalid) {
|
||||
$scope.loginForm.username.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
$scope.loginForm.password.$viewChangeListeners.push(function () {
|
||||
if ($scope.loginForm.password.$invalid) {
|
||||
$scope.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form method="POST" name="loginForm" ng-submit="loginSubmit(login, password)" ng-controller="Umbraco.Dialogs.LoginController">
|
||||
<div ng-controller="Umbraco.Dialogs.LoginController">
|
||||
<div id="login" class="umb-modalcolumn" ng-class="{'show-validation': loginForm.$invalid}">
|
||||
|
||||
<div class="form">
|
||||
@@ -7,20 +7,50 @@
|
||||
<span ng-show="dialogData.isTimedOut"><localize key="login_timeout">Log in below</localize>.</span>
|
||||
<localize key="login_instruction">Log in below</localize>
|
||||
</p>
|
||||
|
||||
<div class="control-group" ng-class="{error: loginForm.username.$invalid}">
|
||||
<input type="text" autofocus ng-model="login" name="username" class="input-xlarge" localize="placeholder" placeholder="@placeholders_username" />
|
||||
</div>
|
||||
|
||||
<div class="control-group" ng-class="{error: loginForm.password.$invalid}">
|
||||
<input type="password" ng-model="password" name="password" class="input-xlarge" localize="placeholder" placeholder="@placeholders_password" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" val-trigger-change="#login .form input"><localize key="general_login">Login</localize></button>
|
||||
|
||||
<div class="control-group" ng-show="loginForm.$invalid">
|
||||
<div class="alert alert-error">{{errorMsg}}</div>
|
||||
<div class="external-logins" ng-if="externalLoginProviders.length > 0">
|
||||
|
||||
<div class="alert alert-error" ng-repeat="error in externalLoginInfo.errors">
|
||||
<span>{{error}}</span>
|
||||
</div>
|
||||
|
||||
<form method="POST" name="externalLoginForm" action="{{externalLoginFormAction}}">
|
||||
|
||||
<div ng-repeat="login in externalLoginProviders">
|
||||
|
||||
<button type="submit" class="btn btn-block btn-social"
|
||||
ng-class="login.properties.SocialStyle"
|
||||
id="{{login.authType}}" name="provider" value="{{login.authType}}"
|
||||
title="Log in using your {{login.caption}} account">
|
||||
<i class="fa" ng-class="login.properties.SocialIcon"></i>
|
||||
Sign in with {{login.caption}}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="hrOr">
|
||||
<hr/><div>Or</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<form method="POST" name="loginForm" ng-submit="loginSubmit(login, password)">
|
||||
<div class="control-group" ng-class="{error: loginForm.username.$invalid}">
|
||||
<input type="text" autofocus ng-model="login" name="username" class="input-xlarge" localize="placeholder" placeholder="@placeholders_username" />
|
||||
</div>
|
||||
|
||||
<div class="control-group" ng-class="{error: loginForm.password.$invalid}">
|
||||
<input type="password" ng-model="password" name="password" class="input-xlarge" localize="placeholder" placeholder="@placeholders_password" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" val-trigger-change="#login .form input"><localize key="general_login">Login</localize></button>
|
||||
|
||||
<div class="control-group" ng-show="loginForm.$invalid">
|
||||
<div class="alert alert-error">{{errorMsg}}</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,10 +1,12 @@
|
||||
angular.module("umbraco")
|
||||
.controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService) {
|
||||
.controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource) {
|
||||
|
||||
$scope.user = userService.getCurrentUser();
|
||||
$scope.history = historyService.getCurrent();
|
||||
$scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
|
||||
|
||||
$scope.externalLoginProviders = externalLoginInfo.providers;
|
||||
$scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
|
||||
|
||||
var evtHandlers = [];
|
||||
evtHandlers.push(eventsService.on("historyService.add", function (e, args) {
|
||||
$scope.history = args.all;
|
||||
@@ -49,16 +51,49 @@ angular.module("umbraco")
|
||||
}, 1000, false); // 1 second, do NOT execute a global digest
|
||||
}
|
||||
|
||||
//get the user
|
||||
userService.getCurrentUser().then(function (user) {
|
||||
$scope.user = user;
|
||||
if ($scope.user) {
|
||||
$scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
|
||||
$scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
|
||||
//set the timer
|
||||
updateTimeout();
|
||||
function updateUserInfo() {
|
||||
//get the user
|
||||
userService.getCurrentUser().then(function (user) {
|
||||
$scope.user = user;
|
||||
if ($scope.user) {
|
||||
$scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
|
||||
$scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
|
||||
//set the timer
|
||||
updateTimeout();
|
||||
|
||||
authResource.getCurrentUserLinkedLogins().then(function(logins) {
|
||||
//reset all to be un-linked
|
||||
for (var provider in $scope.externalLoginProviders) {
|
||||
$scope.externalLoginProviders[provider].linkedProviderKey = undefined;
|
||||
}
|
||||
|
||||
//set the linked logins
|
||||
for (var login in logins) {
|
||||
var found = _.find($scope.externalLoginProviders, function (i) {
|
||||
return i.authType == login;
|
||||
});
|
||||
if (found) {
|
||||
found.linkedProviderKey = logins[login];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unlink = function (e, loginProvider, providerKey) {
|
||||
var result = confirm("Are you sure you want to unlink this account?");
|
||||
if (!result) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
|
||||
updateUserInfo();
|
||||
});
|
||||
}
|
||||
|
||||
updateUserInfo();
|
||||
|
||||
//remove all event handlers
|
||||
$scope.$on('$destroy', function () {
|
||||
|
||||
@@ -1,48 +1,81 @@
|
||||
<div class="umb-panel" ng-controller="Umbraco.Dialogs.UserController">
|
||||
<div class="umb-panel-header">
|
||||
<div class="umb-el-wrap umb-panel-buttons">
|
||||
<div class="btn-toolbar umb-btn-toolbar">
|
||||
<button ng-click="logout()" hotkey="ctrl+shift+l" class="btn btn-warning">
|
||||
<localize key="general_logout">Log out</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="headline">{{user.name}}</h1>
|
||||
<p class="muted">
|
||||
<small>
|
||||
<localize key="user_sessionExpires" />: {{remainingAuthSeconds | timespan}}</small>
|
||||
</p>
|
||||
<div class="umb-panel umb-user-panel" ng-controller="Umbraco.Dialogs.UserController">
|
||||
<div class="umb-panel-header">
|
||||
<div class="umb-el-wrap umb-panel-buttons">
|
||||
<div class="btn-toolbar umb-btn-toolbar">
|
||||
<button ng-click="logout()" hotkey="ctrl+shift+l" class="btn btn-warning">
|
||||
<localize key="general_logout">Log out</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="headline">{{user.name}}</h1>
|
||||
<p class="muted">
|
||||
<small>
|
||||
<localize key="user_sessionExpires" />: {{remainingAuthSeconds | timespan}}
|
||||
</small>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="umb-panel-body umb-scrollable">
|
||||
<div class="tab-content umb-control-group">
|
||||
|
||||
<div class="umb-pane" ng-if="canEditProfile">
|
||||
<h5><localize key="user_yourProfile" /></h5>
|
||||
<p>
|
||||
<a
|
||||
href="#/users/framed/%252Fumbraco%252Fusers%252Fedituser.aspx%253Fid%253D{{user.id}}"
|
||||
class="btn btn-primary"
|
||||
ng-click="close()">
|
||||
<localize key="general_edit">Edit</localize>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-panel-body umb-scrollable">
|
||||
<div class="tab-content umb-control-group">
|
||||
|
||||
<div class="umb-pane">
|
||||
<h5><localize key="user_yourHistory" /></h5>
|
||||
<ul class="umb-tree">
|
||||
<li ng-repeat="item in history | orderBy:'time':true">
|
||||
<a ng-href="{{item.link}}" ng-click="gotoHistory(item.link)" prevent-default>
|
||||
<i class="{{item.icon}}"></i> {{item.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="umb-pane" ng-if="canEditProfile">
|
||||
<h5><localize key="user_yourProfile" /></h5>
|
||||
<p>
|
||||
<a href="#/users/framed/%252Fumbraco%252Fusers%252Fedituser.aspx%253Fid%253D{{user.id}}"
|
||||
class="btn btn-primary"
|
||||
ng-click="close()">
|
||||
<localize key="general_edit">Edit</localize>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="umb-pane external-logins" ng-if="externalLoginProviders.length > 0">
|
||||
|
||||
<small class="umb-version">Umbraco version {{version}}</small>
|
||||
</div>
|
||||
<h5>External login providers</h5>
|
||||
|
||||
<div ng-repeat="login in externalLoginProviders">
|
||||
|
||||
<form ng-if="login.linkedProviderKey == undefined" method="POST" name="externalLoginForm"
|
||||
action="{{externalLinkLoginFormAction}}">
|
||||
|
||||
<button class="btn btn-block btn-social"
|
||||
ng-class="login.properties.SocialStyle"
|
||||
id="{{login.authType}}"
|
||||
name="provider"
|
||||
value="{{login.authType}}">
|
||||
<i class="fa" ng-class="login.properties.SocialIcon"></i>
|
||||
Link your {{login.caption}} account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button ng-if="login.linkedProviderKey != undefined"
|
||||
ng-click="unlink($event, login.authType, login.linkedProviderKey)"
|
||||
class="btn btn-block btn-social"
|
||||
ng-class="login.properties.SocialStyle"
|
||||
id="{{login.authType}}"
|
||||
name="provider"
|
||||
value="{{login.authType}}">
|
||||
<i class="fa" ng-class="login.properties.SocialIcon"></i>
|
||||
Un-link your {{login.caption}} account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="umb-pane">
|
||||
<h5><localize key="user_yourHistory" /></h5>
|
||||
<ul class="umb-tree">
|
||||
<li ng-repeat="item in history | orderBy:'time':true">
|
||||
<a ng-href="{{item.link}}" ng-click="gotoHistory(item.link)" prevent-default>
|
||||
<i class="{{item.icon}}"></i> {{item.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<small class="umb-version">Umbraco version {{version}}</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +151,35 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core">
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.Owin">
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.Cookies, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Threading.Tasks">
|
||||
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.165\lib\net45\Microsoft.Threading.Tasks.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -178,6 +206,10 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System">
|
||||
<Name>System</Name>
|
||||
</Reference>
|
||||
@@ -199,6 +231,7 @@
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.EnterpriseServices" />
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
|
||||
@@ -10,7 +10,7 @@ NOTES:
|
||||
* Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config
|
||||
* A new version will invalidate both client and server cache and create new persisted files
|
||||
-->
|
||||
<clientDependency version="366385329" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
|
||||
<clientDependency version="1465049132" fileDependencyExtensions=".js,.css" loggerType="Umbraco.Web.UI.CdfLogger, umbraco">
|
||||
|
||||
<!--
|
||||
This section is used for Web Forms only, the enableCompositeFiles="true" is optional and by default is set to true.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<package id="ImageProcessor.Web" version="3.3.1.0" targetFramework="net45" />
|
||||
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
|
||||
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Core" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
|
||||
@@ -21,10 +23,16 @@
|
||||
<package id="Microsoft.Bcl.Async" version="1.0.165" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.28" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.Cookies" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
|
||||
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
|
||||
<package id="MySql.Data" version="6.6.5" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="SqlServerCE" version="4.0.0.0" targetFramework="net40" />
|
||||
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net40" />
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Collections
|
||||
@using System.Net.Http
|
||||
@using System.Web.Mvc.Html
|
||||
@using Umbraco.Core
|
||||
@using ClientDependency.Core
|
||||
@using ClientDependency.Core.Mvc
|
||||
@using Microsoft.Owin.Security
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Linq
|
||||
@using Umbraco.Core.IO
|
||||
@using Umbraco.Web
|
||||
@using Umbraco.Web.Editors
|
||||
@@ -23,17 +27,22 @@
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<title ng-bind="$root.locationTitle">Umbraco</title>
|
||||
|
||||
@{ Html.RequiresCss("assets/css/umbraco.css", "Umbraco");}
|
||||
@{ Html.RequiresCss("tree/treeicons.css", "UmbracoClient");}
|
||||
|
||||
@{
|
||||
Html
|
||||
.RequiresCss("assets/css/umbraco.css", "Umbraco")
|
||||
.RequiresCss("tree/treeicons.css", "UmbracoClient")
|
||||
.RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco")
|
||||
.RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco");
|
||||
}
|
||||
@Html.RenderCssHere(
|
||||
new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
|
||||
new BasicPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient)))
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body ng-class="{touch:touchDevice,emptySection:emptySection}" ng-controller="Umbraco.MainController" id="umbracoMainPageBody">
|
||||
<div ng-hide="!authenticated" ng-cloak id="mainwrapper" id="mainwrapper" class="clearfix" ng-click="closeDialogs($event)">
|
||||
<div ng-hide="!authenticated" ng-cloak id="mainwrapper" id="mainwrapper" class="clearfix" ng-click="closeDialogs($event)">
|
||||
|
||||
<umb-navigation></umb-navigation>
|
||||
|
||||
@@ -47,28 +56,63 @@
|
||||
<umb-notifications></umb-notifications>
|
||||
|
||||
|
||||
@{
|
||||
var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes()
|
||||
.Where(p => p.Properties.ContainsKey("UmbracoBackOffice"))
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.AuthenticationType,
|
||||
caption = p.Caption,
|
||||
properties = p.Properties
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@*
|
||||
These are the bare minimal server variables that are required for the application to start without being authenticated,
|
||||
we will load the rest of the server vars after the user is authenticated.
|
||||
*@
|
||||
*@
|
||||
<script type="text/javascript">
|
||||
var Umbraco = {};
|
||||
Umbraco.Sys = {};
|
||||
Umbraco.Sys.ServerVariables = {
|
||||
"umbracoUrls": {
|
||||
"umbracoUrls": {
|
||||
"authenticationApiBaseUrl": "@(Url.GetUmbracoApiServiceBaseUrl<AuthenticationController>(controller => controller.PostLogin(null)))",
|
||||
"serverVarsJs": '@Url.GetUrlWithCacheBust("ServerVariables", "BackOffice")'
|
||||
},
|
||||
"application": {
|
||||
"applicationPath" : "@Context.Request.ApplicationPath"
|
||||
"serverVarsJs": "@Url.GetUrlWithCacheBust("ServerVariables", "BackOffice")",
|
||||
"externalLoginsUrl": "@(Url.Action("ExternalLogin", "BackOffice", new {area = ViewBag.UmbracoPath}))"
|
||||
},
|
||||
"application": {
|
||||
"applicationPath" : "@Context.Request.ApplicationPath"
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
//define a callback that is executed when we bootstrap angular, this is used to inject angular values
|
||||
//with server side info
|
||||
document.angularReady = function(app) {
|
||||
//add external login values
|
||||
var errors = [];
|
||||
@if (ViewBag.ExternalSignInError != null)
|
||||
{
|
||||
foreach (var error in ViewBag.ExternalSignInError)
|
||||
{
|
||||
<text>errors.push("@error");</text>
|
||||
}
|
||||
}
|
||||
app.value("externalLoginInfo", {
|
||||
errors: errors,
|
||||
providers: @Html.Raw(JsonConvert.SerializeObject(loginProviders))
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@*And finally we can load in our angular app*@
|
||||
<script type="text/javascript" src="lib/rgrove-lazyload/lazyload.js"></script>
|
||||
<script type="text/javascript" src="@Url.GetUrlWithCacheBust("Application", "BackOffice")"></script>
|
||||
|
||||
|
||||
@{
|
||||
var isDebug = false;
|
||||
if (Request.RawUrl.IndexOf('?') >= 0)
|
||||
@@ -78,9 +122,9 @@
|
||||
if (attempt && attempt.Result)
|
||||
{
|
||||
isDebug = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@if (isDebug)
|
||||
{
|
||||
@@ -89,4 +133,3 @@
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@
|
||||
<add key="enableSimpleMembership" value="false" />
|
||||
<add key="autoFormsAuthentication" value="false" />
|
||||
<add key="log4net.Config" value="config\log4net.config" />
|
||||
<add key="owin:appStartup" value="UmbracoDefaultOwinStartup" xdt:Transform="InsertIfMissing" xdt:Locator="Match(key)" />
|
||||
</appSettings>
|
||||
|
||||
|
||||
<connectionStrings xdt:Transform="Remove" xdt:Locator="Condition(@configSource != '')" />
|
||||
<connectionStrings xdt:Transform="InsertIfMissing">
|
||||
<remove name="umbracoDbDSN" />
|
||||
@@ -171,6 +172,34 @@
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly xdt:Transform="Remove"
|
||||
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Microsoft.Owin']])" />
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly xdt:Transform="Remove"
|
||||
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Microsoft.Owin.Security']])" />
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly xdt:Transform="Remove"
|
||||
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Microsoft.Owin.Security.Cookies']])" />
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly xdt:Transform="Remove"
|
||||
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Microsoft.Owin.Security.OAuth']])" />
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
</assemblyBinding>
|
||||
|
||||
</runtime>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<add key="autoFormsAuthentication" value="false" />
|
||||
|
||||
<add key="log4net.Config" value="config\log4net.config" />
|
||||
|
||||
<add key="owin:appStartup" value="UmbracoDefaultOwinStartup" />
|
||||
</appSettings>
|
||||
<connectionStrings>
|
||||
<remove name="umbracoDbDSN" />
|
||||
@@ -263,6 +263,23 @@
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Umbraco.Web.BaseRest
|
||||
/// <returns>A value indicating whether the specified Uri should be routed to the BaseRestHandler.</returns>
|
||||
public static bool IsBaseRestRequest(Uri uri)
|
||||
{
|
||||
if (uri == null) return false;
|
||||
return UmbracoConfig.For.BaseRestExtensions().Enabled
|
||||
&& uri.AbsolutePath.ToLowerInvariant().StartsWith(BaseUrl);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Helpers;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -21,6 +27,10 @@ using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco.providers;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -34,7 +44,14 @@ namespace Umbraco.Web.Editors
|
||||
[IsBackOffice]
|
||||
public class AuthenticationController : UmbracoApiController
|
||||
{
|
||||
|
||||
|
||||
private BackOfficeUserManager _userManager;
|
||||
|
||||
protected BackOfficeUserManager UserManager
|
||||
{
|
||||
get { return _userManager ?? (_userManager = TryGetOwinContext().Result.GetUserManager<BackOfficeUserManager>()); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a special method that will return the current users' remaining session seconds, the reason
|
||||
/// it is special is because this route is ignored in the UmbracoModule so that the auth ticket doesn't get
|
||||
@@ -55,6 +72,27 @@ namespace Umbraco.Web.Editors
|
||||
throw new NotSupportedException("An HttpContext is required for this request");
|
||||
}
|
||||
|
||||
[WebApi.UmbracoAuthorize]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<HttpResponseMessage> PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
|
||||
{
|
||||
var result = await UserManager.RemoveLoginAsync(
|
||||
User.Identity.GetUserId<int>(),
|
||||
new UserLoginInfo(unlinkLoginModel.LoginProvider, unlinkLoginModel.ProviderKey));
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId<int>());
|
||||
await SignInAsync(user, isPersistent: false);
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModelErrors(result);
|
||||
return Request.CreateValidationErrorResponse(ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest)
|
||||
/// </summary>
|
||||
@@ -92,34 +130,54 @@ namespace Umbraco.Web.Editors
|
||||
//set their remaining seconds
|
||||
result.SecondsUntilTimeout = httpContextAttempt.Result.GetRemainingAuthSeconds();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[WebApi.UmbracoAuthorize]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<Dictionary<string, string>> GetCurrentUserLinkedLogins()
|
||||
{
|
||||
var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId());
|
||||
return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a user in
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public UserDetail PostLogin(LoginModel loginModel)
|
||||
public async Task<HttpResponseMessage> PostLogin(LoginModel loginModel)
|
||||
{
|
||||
if (UmbracoContext.Security.ValidateBackOfficeCredentials(loginModel.Username, loginModel.Password))
|
||||
{
|
||||
//get the user
|
||||
var user = Security.GetBackOfficeUser(loginModel.Username);
|
||||
var userDetail = Mapper.Map<UserDetail>(user);
|
||||
|
||||
//TODO: Clean up the int cast!
|
||||
var ticket = UmbracoContext.Security.PerformLogin(user);
|
||||
//create a response with the userDetail object
|
||||
var response = Request.CreateResponse(HttpStatusCode.OK, userDetail);
|
||||
|
||||
//set the response cookies with the ticket (NOTE: This needs to be done with the custom webapi extension because
|
||||
// we cannot mix HttpContext.Response.Cookies and the way WebApi/Owin work)
|
||||
var ticket = response.UmbracoLoginWebApi(user);
|
||||
|
||||
//Identity does some of it's own checks as well so we need to use it's sign in process too... this will essentially re-create the
|
||||
// ticket/cookie above but we need to create the ticket now so we can assign the Current Thread User/IPrinciple below
|
||||
await SignInAsync(Mapper.Map<IUser, BackOfficeIdentityUser>(user), isPersistent: true);
|
||||
|
||||
var http = this.TryGetHttpContext();
|
||||
if (http.Success == false)
|
||||
{
|
||||
throw new InvalidOperationException("This method requires that an HttpContext be active");
|
||||
}
|
||||
//This ensure the current principal is set, otherwise any logic executing after this wouldn't actually be authenticated
|
||||
http.Result.AuthenticateCurrentRequest(ticket, false);
|
||||
|
||||
//update the userDetail and set their remaining seconds
|
||||
userDetail.SecondsUntilTimeout = ticket.GetRemainingAuthSeconds();
|
||||
|
||||
var result = Mapper.Map<UserDetail>(user);
|
||||
//set their remaining seconds
|
||||
result.SecondsUntilTimeout = ticket.GetRemainingAuthSeconds();
|
||||
return result;
|
||||
return response;
|
||||
}
|
||||
|
||||
//return BadRequest (400), we don't want to return a 401 because that get's intercepted
|
||||
@@ -141,5 +199,25 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private void AddModelErrors(IdentityResult result, string prefix = "")
|
||||
{
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(prefix, error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent)
|
||||
{
|
||||
var owinContext = TryGetOwinContext().Result;
|
||||
|
||||
owinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
owinContext.Authentication.SignIn(
|
||||
new AuthenticationProperties() { IsPersistent = isPersistent },
|
||||
await user.GenerateUserIdentityAsync(UserManager));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,17 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.ServiceModel.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.UI;
|
||||
using dotless.Core.Parser.Tree;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -27,6 +33,13 @@ using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.WebServices;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using System.Web;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
using Umbraco.Web.Security.Identity;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -37,13 +50,40 @@ namespace Umbraco.Web.Editors
|
||||
[DisableClientCache]
|
||||
public class BackOfficeController : UmbracoController
|
||||
{
|
||||
private BackOfficeUserManager _userManager;
|
||||
|
||||
protected BackOfficeUserManager UserManager
|
||||
{
|
||||
get { return _userManager ?? (_userManager = OwinContext.GetUserManager<BackOfficeUserManager>()); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render the default view
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ActionResult Default()
|
||||
public async Task<ActionResult> Default()
|
||||
{
|
||||
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml");
|
||||
ViewBag.UmbracoPath = GlobalSettings.UmbracoMvcArea;
|
||||
|
||||
//check if there's errors in the TempData, assign to view bag and render the view
|
||||
if (TempData["ExternalSignInError"] != null)
|
||||
{
|
||||
ViewBag.ExternalSignInError = TempData["ExternalSignInError"];
|
||||
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml");
|
||||
}
|
||||
|
||||
//First check if there's external login info, if there's not proceed as normal
|
||||
var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync(
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
if (loginInfo == null)
|
||||
{
|
||||
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml");
|
||||
}
|
||||
|
||||
//we're just logging in with an external source, not linking accounts
|
||||
return await ExternalSignInAsync(loginInfo);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,6 +234,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
"umbracoUrls", new Dictionary<string, object>
|
||||
{
|
||||
{"externalLoginsUrl", Url.Action("ExternalLogin", "BackOffice")},
|
||||
{"externalLinkLoginsUrl", Url.Action("LinkLogin", "BackOffice")},
|
||||
{"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")},
|
||||
{"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")},
|
||||
{"gridConfig", Url.Action("GetGridConfig", "BackOffice")},
|
||||
@@ -276,10 +318,10 @@ namespace Umbraco.Web.Editors
|
||||
controller => controller.Fetch(string.Empty))
|
||||
},
|
||||
{
|
||||
"relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RelationController>(
|
||||
controller => controller.GetById(0))
|
||||
},
|
||||
{
|
||||
"relationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RelationController>(
|
||||
controller => controller.GetById(0))
|
||||
},
|
||||
{
|
||||
"rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RichTextPreValueController>(
|
||||
controller => controller.GetConfiguration())
|
||||
},
|
||||
@@ -345,7 +387,25 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
},
|
||||
{"isDebuggingEnabled", HttpContext.IsDebuggingEnabled},
|
||||
{"application", GetApplicationState()}
|
||||
{
|
||||
"application", GetApplicationState()
|
||||
},
|
||||
{
|
||||
"externalLogins", new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"providers", HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes()
|
||||
.Where(p => p.Properties.ContainsKey("UmbracoBackOffice"))
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.AuthenticationType, caption = p.Caption,
|
||||
//TODO: Need to see if this exposes any sensitive data!
|
||||
properties = p.Properties
|
||||
})
|
||||
.ToArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//cache the result if debugging is disabled
|
||||
@@ -359,6 +419,96 @@ namespace Umbraco.Web.Editors
|
||||
return JavaScript(ServerVariablesParser.Parse(result));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult ExternalLogin(string provider)
|
||||
{
|
||||
// Request a redirect to the external login provider
|
||||
return new ChallengeResult(provider,
|
||||
Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
[UmbracoAuthorize]
|
||||
[HttpPost]
|
||||
public ActionResult LinkLogin(string provider)
|
||||
{
|
||||
// Request a redirect to the external login provider to link a login for the current user
|
||||
return new ChallengeResult(provider,
|
||||
Url.Action("ExternalLinkLoginCallback", "BackOffice"),
|
||||
User.Identity.GetUserId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> ExternalLinkLoginCallback()
|
||||
{
|
||||
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType,
|
||||
XsrfKey, User.Identity.GetUserId());
|
||||
|
||||
if (loginInfo == null)
|
||||
{
|
||||
//Add error and redirect for it to be displayed
|
||||
TempData["ExternalSignInError"] = new[] { "An error occurred, could not get external login info" };
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId<int>(), loginInfo.Login);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
//Add errors and redirect for it to be displayed
|
||||
TempData["ExternalSignInError"] = result.Errors;
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
private async Task<ActionResult> ExternalSignInAsync(ExternalLoginInfo loginInfo)
|
||||
{
|
||||
if (loginInfo == null) throw new ArgumentNullException("loginInfo");
|
||||
|
||||
// Sign in the user with this external login provider if the user already has a login
|
||||
var user = await UserManager.FindAsync(loginInfo.Login);
|
||||
if (user != null)
|
||||
{
|
||||
//TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we
|
||||
// wouldn't necessarily sign the user in here with the standard login, instead we'd update the
|
||||
// UseUmbracoBackOfficeExternalCookieAuthentication extension method to have the correct provider and claims factory,
|
||||
// ticket format, etc.. to create our back office user including the claims assigned and in this method we'd just ensure
|
||||
// that the ticket is created and stored and that the user is logged in.
|
||||
|
||||
//sign in
|
||||
await SignInAsync(user, isPersistent: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" };
|
||||
|
||||
//Remove the cookie otherwise this message will keep appearing
|
||||
if (Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName] != null)
|
||||
{
|
||||
Response.Cookies[Core.Constants.Security.BackOfficeExternalCookieName].Expires = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml");
|
||||
}
|
||||
|
||||
private async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent)
|
||||
{
|
||||
OwinContext.Authentication.SignOut(Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
OwinContext.Authentication.SignIn(
|
||||
new AuthenticationProperties() {IsPersistent = isPersistent},
|
||||
await user.GenerateUserIdentityAsync(UserManager));
|
||||
}
|
||||
|
||||
private IAuthenticationManager AuthenticationManager
|
||||
{
|
||||
get { return OwinContext.Authentication; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the server variables regarding the application state
|
||||
/// </summary>
|
||||
@@ -505,5 +655,43 @@ namespace Umbraco.Web.Editors
|
||||
JsUrl
|
||||
}
|
||||
|
||||
private ActionResult RedirectToLocal(string returnUrl)
|
||||
{
|
||||
if (Url.IsLocalUrl(returnUrl))
|
||||
{
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
// Used for XSRF protection when adding external logins
|
||||
private const string XsrfKey = "XsrfId";
|
||||
|
||||
private class ChallengeResult : HttpUnauthorizedResult
|
||||
{
|
||||
public ChallengeResult(string provider, string redirectUri, string userId = null)
|
||||
{
|
||||
LoginProvider = provider;
|
||||
RedirectUri = redirectUri;
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
private string LoginProvider { get; set; }
|
||||
private string RedirectUri { get; set; }
|
||||
private string UserId { get; set; }
|
||||
|
||||
public override void ExecuteResult(ControllerContext context)
|
||||
{
|
||||
//Ensure the forms auth module doesn't do a redirect!
|
||||
context.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
|
||||
|
||||
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri.EnsureEndsWith('/') };
|
||||
if (UserId != null)
|
||||
{
|
||||
properties.Dictionary[XsrfKey] = UserId;
|
||||
}
|
||||
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/Umbraco.Web/IUmbracoContextAccessor.cs
Normal file
13
src/Umbraco.Web/IUmbracoContextAccessor.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to retrieve the Umbraco context
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: We could expose this to make working with UmbracoContext easier if we were to use it throughout the codebase
|
||||
/// </remarks>
|
||||
internal interface IUmbracoContextAccessor
|
||||
{
|
||||
UmbracoContext Value { get; }
|
||||
}
|
||||
}
|
||||
@@ -42,5 +42,7 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
/// </summary>
|
||||
[DataMember(Name = "allowedSections")]
|
||||
public IEnumerable<string> AllowedSections { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ using Umbraco.Core.Models.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using umbraco;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
@@ -17,7 +20,19 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias))
|
||||
.ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId))
|
||||
.ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => ui.Culture(user)))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)))
|
||||
.ForMember(
|
||||
detail => detail.EmailHash,
|
||||
opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5()))
|
||||
.ForMember(detail => detail.SecondsUntilTimeout, opt => opt.Ignore());
|
||||
|
||||
config.CreateMap<BackOfficeIdentityUser, UserDetail>()
|
||||
.ForMember(detail => detail.UserId, opt => opt.MapFrom(user => user.Id))
|
||||
.ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserTypeAlias))
|
||||
.ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId))
|
||||
.ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture))
|
||||
.ForMember(detail => detail.AllowedSections, opt => opt.MapFrom(user => user.AllowedSections))
|
||||
.ForMember(
|
||||
detail => detail.EmailHash,
|
||||
opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5()))
|
||||
@@ -25,6 +40,18 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
config.CreateMap<IProfile, UserBasic>()
|
||||
.ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id)));
|
||||
|
||||
config.CreateMap<IUser, UserData>()
|
||||
.ConstructUsing((IUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
|
||||
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
|
||||
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
|
||||
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
|
||||
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => new[] {user.UserType.Alias}))
|
||||
.ForMember(detail => detail.StartContentNode, opt => opt.MapFrom(user => user.StartContentId))
|
||||
.ForMember(detail => detail.StartMediaNode, opt => opt.MapFrom(user => user.StartMediaId))
|
||||
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username))
|
||||
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService)));
|
||||
|
||||
}
|
||||
|
||||
private static int GetIntId(object id)
|
||||
|
||||
16
src/Umbraco.Web/Models/UnLinkLoginModel.cs
Normal file
16
src/Umbraco.Web/Models/UnLinkLoginModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
public class UnLinkLoginModel
|
||||
{
|
||||
[Required]
|
||||
[DataMember(Name = "loginProvider", IsRequired = true)]
|
||||
public string LoginProvider { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataMember(Name = "providerKey", IsRequired = true)]
|
||||
public string ProviderKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ namespace Umbraco.Web.Mvc
|
||||
{
|
||||
if (filterContext.IsChildAction) base.OnResultExecuting(filterContext);
|
||||
|
||||
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
|
||||
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.Now.AddDays(-10));
|
||||
filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now);
|
||||
filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
|
||||
filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
|
||||
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -24,6 +26,11 @@ namespace Umbraco.Web.Mvc
|
||||
|
||||
}
|
||||
|
||||
protected IOwinContext OwinContext
|
||||
{
|
||||
get { return Request.GetOwinContext(); }
|
||||
}
|
||||
|
||||
private UmbracoHelper _umbraco;
|
||||
|
||||
/// <summary>
|
||||
|
||||
165
src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
Normal file
165
src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Extensions;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
public static class AppBuilderExtensions
|
||||
{
|
||||
#region Backoffice
|
||||
|
||||
/// <summary>
|
||||
/// Configure Default Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="appContext"></param>
|
||||
/// <param name="userMembershipProvider"></param>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
ApplicationContext appContext,
|
||||
MembershipProviderBase userMembershipProvider)
|
||||
{
|
||||
if (appContext == null) throw new ArgumentNullException("appContext");
|
||||
if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider");
|
||||
|
||||
//Don't proceed if the app is not ready
|
||||
if (appContext.IsConfigured == false
|
||||
|| appContext.DatabaseContext == null
|
||||
|| appContext.DatabaseContext.IsDatabaseConfigured == false) return;
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeUserManager>(
|
||||
(options, owinContext) => BackOfficeUserManager.Create(
|
||||
options,
|
||||
appContext.Services.UserService,
|
||||
appContext.Services.ExternalLoginService,
|
||||
userMembershipProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure a custom UserStore with the Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="appContext"></param>
|
||||
/// <param name="userMembershipProvider"></param>
|
||||
/// <param name="customUserStore"></param>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
ApplicationContext appContext,
|
||||
MembershipProviderBase userMembershipProvider,
|
||||
BackOfficeUserStore customUserStore)
|
||||
{
|
||||
if (appContext == null) throw new ArgumentNullException("appContext");
|
||||
if (userMembershipProvider == null) throw new ArgumentNullException("userMembershipProvider");
|
||||
if (customUserStore == null) throw new ArgumentNullException("customUserStore");
|
||||
|
||||
//Don't proceed if the app is not ready
|
||||
if (appContext.IsConfigured == false
|
||||
|| appContext.DatabaseContext == null
|
||||
|| appContext.DatabaseContext.IsDatabaseConfigured == false) return;
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeUserManager>(
|
||||
(options, owinContext) => BackOfficeUserManager.Create(
|
||||
options,
|
||||
customUserStore,
|
||||
userMembershipProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure a custom BackOfficeUserManager for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="appContext"></param>
|
||||
/// <param name="userManager"></param>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice<TManager, TUser>(this IAppBuilder app,
|
||||
ApplicationContext appContext,
|
||||
Func<IdentityFactoryOptions<TManager>, IOwinContext, TManager> userManager)
|
||||
where TManager : BackOfficeUserManager<TUser>
|
||||
where TUser : BackOfficeIdentityUser
|
||||
{
|
||||
if (appContext == null) throw new ArgumentNullException("appContext");
|
||||
if (userManager == null) throw new ArgumentNullException("userManager");
|
||||
|
||||
//Don't proceed if the app is not ready
|
||||
if (appContext.IsConfigured == false
|
||||
|| appContext.DatabaseContext == null
|
||||
|| appContext.DatabaseContext.IsDatabaseConfigured == false) return;
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<TManager>(userManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException("app");
|
||||
|
||||
|
||||
app.UseCookieAuthentication(new UmbracoBackOfficeCookieAuthenticationOptions(
|
||||
UmbracoConfig.For.UmbracoSettings().Security,
|
||||
GlobalSettings.TimeOutInMinutes,
|
||||
GlobalSettings.UseSSL)
|
||||
{
|
||||
Provider = new CookieAuthenticationProvider
|
||||
{
|
||||
// Enables the application to validate the security stamp when the user
|
||||
// logs in. This is a security feature which is used when you
|
||||
// change a password or add an external login to your account.
|
||||
OnValidateIdentity = SecurityStampValidator
|
||||
.OnValidateIdentity<BackOfficeUserManager, BackOfficeIdentityUser, int>(
|
||||
TimeSpan.FromMinutes(30),
|
||||
(manager, user) => user.GenerateUserIdentityAsync(manager),
|
||||
identity => identity.GetUserId<int>())
|
||||
}
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct
|
||||
/// Umbraco back office configuration
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeExternalCookieAuthentication(this IAppBuilder app)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException("app");
|
||||
|
||||
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
||||
{
|
||||
AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
|
||||
AuthenticationMode = AuthenticationMode.Passive,
|
||||
CookieName = Constants.Security.BackOfficeExternalCookieName,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(5),
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor()),
|
||||
CookiePath = "/",
|
||||
CookieSecure = GlobalSettings.UseSSL ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest,
|
||||
CookieHttpOnly = true,
|
||||
CookieDomain = UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.Owin.Security;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
public static class AuthenticationDescriptionOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the properties of the authentication description instance for use with Umbraco back office
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="style"></param>
|
||||
/// <param name="icon"></param>
|
||||
public static void ForUmbracoBackOffice(this AuthenticationOptions options, string style, string icon)
|
||||
{
|
||||
Mandate.ParameterNotNullOrEmpty(options.AuthenticationType, "options.AuthenticationType");
|
||||
|
||||
//Ensure the prefix is set
|
||||
if (options.AuthenticationType.StartsWith(Constants.Security.BackOfficeExternalAuthenticationTypePrefix) == false)
|
||||
{
|
||||
options.AuthenticationType = Constants.Security.BackOfficeExternalAuthenticationTypePrefix + options.AuthenticationType;
|
||||
}
|
||||
|
||||
options.Description.Properties["SocialStyle"] = style;
|
||||
options.Description.Properties["SocialIcon"] = icon;
|
||||
|
||||
//flag for use in back office
|
||||
options.Description.Properties["UmbracoBackOffice"] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
public static class AuthenticationManagerExtensions
|
||||
{
|
||||
private static ExternalLoginInfo GetExternalLoginInfo(AuthenticateResult result)
|
||||
{
|
||||
if (result == null || result.Identity == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (idClaim == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// By default we don't allow spaces in user names
|
||||
var name = result.Identity.Name;
|
||||
if (name != null)
|
||||
{
|
||||
name = name.Replace(" ", "");
|
||||
}
|
||||
var email = result.Identity.FindFirstValue(ClaimTypes.Email);
|
||||
return new ExternalLoginInfo
|
||||
{
|
||||
ExternalIdentity = result.Identity,
|
||||
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value),
|
||||
DefaultUserName = name,
|
||||
Email = email
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts login info out of an external identity
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="authenticationType"></param>
|
||||
/// <param name="xsrfKey">key that will be used to find the userId to verify</param>
|
||||
/// <param name="expectedValue">
|
||||
/// the value expected to be found using the xsrfKey in the AuthenticationResult.Properties
|
||||
/// dictionary
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(this IAuthenticationManager manager,
|
||||
string authenticationType,
|
||||
string xsrfKey, string expectedValue)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
throw new ArgumentNullException("manager");
|
||||
}
|
||||
var result = await manager.AuthenticateAsync(authenticationType);
|
||||
// Verify that the userId is the same as what we expect if requested
|
||||
if (result != null &&
|
||||
result.Properties != null &&
|
||||
result.Properties.Dictionary != null &&
|
||||
result.Properties.Dictionary.ContainsKey(xsrfKey) &&
|
||||
result.Properties.Dictionary[xsrfKey] == expectedValue)
|
||||
{
|
||||
return GetExternalLoginInfo(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts login info out of an external identity
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
/// <param name="authenticationType"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
throw new ArgumentNullException("manager");
|
||||
}
|
||||
return GetExternalLoginInfo(await manager.AuthenticateAsync(authenticationType));
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs
Normal file
45
src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom cookie manager that is used to read the cookie from the request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install and /base therefore we cannot just set the cookie path to be /umbraco,
|
||||
/// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path.
|
||||
/// </remarks>
|
||||
internal class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly implement this so that we filter the request
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
string ICookieManager.GetRequestCookie(IOwinContext context, string key)
|
||||
{
|
||||
if (_umbracoContextAccessor.Value == null || context.Request.Uri.IsClientSideRequest())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return UmbracoModule.ShouldAuthenticateRequest(
|
||||
context.HttpContextFromOwinContext().Request,
|
||||
_umbracoContextAccessor.Value.OriginalRequestUrl) == false
|
||||
//Don't auth request, don't return a cookie
|
||||
? null
|
||||
//Return the default implementation
|
||||
: base.GetRequestCookie(context, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Web.Security;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Security;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Newtonsoft.Json;
|
||||
using Owin;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Custom secure format that uses the old FormsAuthentication format
|
||||
/// </summary>
|
||||
internal class FormsAuthenticationSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
|
||||
{
|
||||
private readonly int _loginTimeoutMinutes;
|
||||
|
||||
public FormsAuthenticationSecureDataFormat(int loginTimeoutMinutes)
|
||||
{
|
||||
_loginTimeoutMinutes = loginTimeoutMinutes;
|
||||
}
|
||||
|
||||
public string Protect(AuthenticationTicket data)
|
||||
{
|
||||
var backofficeIdentity = (UmbracoBackOfficeIdentity)data.Identity;
|
||||
var userDataString = JsonConvert.SerializeObject(backofficeIdentity.UserData);
|
||||
|
||||
var ticket = new FormsAuthenticationTicket(
|
||||
5,
|
||||
data.Identity.Name,
|
||||
data.Properties.IssuedUtc.HasValue ? data.Properties.IssuedUtc.Value.LocalDateTime : DateTime.Now,
|
||||
data.Properties.ExpiresUtc.HasValue ? data.Properties.ExpiresUtc.Value.LocalDateTime : DateTime.Now.AddMinutes(_loginTimeoutMinutes),
|
||||
data.Properties.IsPersistent,
|
||||
userDataString,
|
||||
"/"
|
||||
);
|
||||
|
||||
return FormsAuthentication.Encrypt(ticket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unprotects the cookie
|
||||
/// </summary>
|
||||
/// <param name="protectedText"></param>
|
||||
/// <returns></returns>
|
||||
public AuthenticationTicket Unprotect(string protectedText)
|
||||
{
|
||||
FormsAuthenticationTicket decrypt;
|
||||
try
|
||||
{
|
||||
decrypt = FormsAuthentication.Decrypt(protectedText);
|
||||
if (decrypt == null) return null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var identity = new UmbracoBackOfficeIdentity(decrypt);
|
||||
|
||||
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties
|
||||
{
|
||||
ExpiresUtc = decrypt.Expiration.ToUniversalTime(),
|
||||
IssuedUtc = decrypt.IssueDate.ToUniversalTime(),
|
||||
IsPersistent = decrypt.IsPersistent
|
||||
});
|
||||
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Umbraco.Web/Security/Identity/OwinExtensions.cs
Normal file
19
src/Umbraco.Web/Security/Identity/OwinExtensions.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
internal static class OwinExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Nasty little hack to get httpcontextbase from an owin context
|
||||
/// </summary>
|
||||
/// <param name="owinContext"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpContextBase HttpContextFromOwinContext(this IOwinContext owinContext)
|
||||
{
|
||||
return owinContext.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Owin.Security.Cookies;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
|
||||
namespace Umbraco.Web.Security.Identity
|
||||
{
|
||||
/// <summary>
|
||||
/// Umbraco auth cookie options
|
||||
/// </summary>
|
||||
public sealed class UmbracoBackOfficeCookieAuthenticationOptions : CookieAuthenticationOptions
|
||||
{
|
||||
public UmbracoBackOfficeCookieAuthenticationOptions()
|
||||
: this(UmbracoConfig.For.UmbracoSettings().Security, GlobalSettings.TimeOutInMinutes, GlobalSettings.UseSSL)
|
||||
{
|
||||
}
|
||||
|
||||
public UmbracoBackOfficeCookieAuthenticationOptions(
|
||||
ISecuritySection securitySection,
|
||||
int loginTimeoutMinutes,
|
||||
bool forceSsl,
|
||||
bool useLegacyFormsAuthDataFormat = true)
|
||||
{
|
||||
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
|
||||
|
||||
if (useLegacyFormsAuthDataFormat)
|
||||
{
|
||||
//If this is not explicitly set it will fall back to the default automatically
|
||||
TicketDataFormat = new FormsAuthenticationSecureDataFormat(loginTimeoutMinutes);
|
||||
}
|
||||
|
||||
CookieDomain = securitySection.AuthCookieDomain;
|
||||
CookieName = securitySection.AuthCookieName;
|
||||
CookieHttpOnly = true;
|
||||
CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest;
|
||||
|
||||
CookiePath = "/";
|
||||
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -91,21 +92,21 @@ namespace Umbraco.Web.Security
|
||||
/// Logs the user in
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns>returns the number of seconds until their session times out</returns>
|
||||
/// <returns>returns the Forms Auth ticket created which is used to log them in</returns>
|
||||
public virtual FormsAuthenticationTicket PerformLogin(IUser user)
|
||||
{
|
||||
var ticket = _httpContext.CreateUmbracoAuthTicket(new UserData(Guid.NewGuid().ToString("N"))
|
||||
//clear the external cookie - we do this without owin context because we're writing cookies directly to httpcontext
|
||||
// and cookie handling is different with httpcontext vs webapi and owin, normally we'd do:
|
||||
//_httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
var externalLoginCookie = _httpContext.Request.Cookies.Get(Constants.Security.BackOfficeExternalCookieName);
|
||||
if (externalLoginCookie != null)
|
||||
{
|
||||
Id = user.Id,
|
||||
AllowedApplications = user.AllowedSections.ToArray(),
|
||||
RealName = user.Name,
|
||||
//currently we only have one user type!
|
||||
Roles = new[] { user.UserType.Alias },
|
||||
StartContentNode = user.StartContentId,
|
||||
StartMediaNode = user.StartMediaId,
|
||||
Username = user.Username,
|
||||
Culture = ui.Culture(user)
|
||||
});
|
||||
externalLoginCookie.Expires = DateTime.Now.AddYears(-1);
|
||||
_httpContext.Response.Cookies.Set(externalLoginCookie);
|
||||
}
|
||||
|
||||
var ticket = _httpContext.CreateUmbracoAuthTicket(Mapper.Map<UserData>(user));
|
||||
|
||||
LogHelper.Info<WebSecurity>("User Id: {0} logged in", () => user.Id);
|
||||
|
||||
|
||||
10
src/Umbraco.Web/SingletonUmbracoContextAccessor.cs
Normal file
10
src/Umbraco.Web/SingletonUmbracoContextAccessor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal class SingletonUmbracoContextAccessor : IUmbracoContextAccessor
|
||||
{
|
||||
public UmbracoContext Value
|
||||
{
|
||||
get { return UmbracoContext.Current; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ LazyLoad.js("##JsInitialize##", function () {
|
||||
UmbClientMgr.setUmbracoPath('"##UmbracoPath##"');
|
||||
|
||||
jQuery(document).ready(function () {
|
||||
|
||||
angular.bootstrap(document, ['umbraco']);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -131,7 +131,35 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AspNet.Identity.Owin, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Host.SystemWeb, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.Cookies, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin.Security.OAuth, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
|
||||
@@ -148,6 +176,10 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System">
|
||||
<Name>System</Name>
|
||||
</Reference>
|
||||
@@ -274,6 +306,8 @@
|
||||
</Compile>
|
||||
<Compile Include="ApplicationContextExtensions.cs" />
|
||||
<Compile Include="AreaRegistrationContextExtensions.cs" />
|
||||
<Compile Include="UmbracoDefaultOwinStartup.cs" />
|
||||
<Compile Include="IUmbracoContextAccessor.cs" />
|
||||
<Compile Include="Models\ContentEditing\Relation.cs" />
|
||||
<Compile Include="HtmlStringUtilities.cs" />
|
||||
<Compile Include="Models\ContentEditing\RelationType.cs" />
|
||||
@@ -286,6 +320,7 @@
|
||||
<Compile Include="Models\LegacyConvertedNodeProperty.cs" />
|
||||
<Compile Include="Models\Mapping\RelationModelMapper.cs" />
|
||||
<Compile Include="Mvc\DisableClientCacheAttribute.cs" />
|
||||
<Compile Include="Models\UnLinkLoginModel.cs" />
|
||||
<Compile Include="Mvc\MvcVersionCheck.cs" />
|
||||
<Compile Include="Mvc\ReflectedFixedRazorViewEngine.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunner.cs" />
|
||||
@@ -518,7 +553,15 @@
|
||||
<Compile Include="Scheduling\IBackgroundTaskRunner.cs" />
|
||||
<Compile Include="Scheduling\ILatchedBackgroundTask.cs" />
|
||||
<Compile Include="Scheduling\RecurringTaskBase.cs" />
|
||||
<Compile Include="Security\Identity\AppBuilderExtensions.cs" />
|
||||
<Compile Include="Security\Identity\AuthenticationDescriptionOptionsExtensions.cs" />
|
||||
<Compile Include="Security\Identity\AuthenticationManagerExtensions.cs" />
|
||||
<Compile Include="Security\Identity\BackOfficeCookieManager.cs" />
|
||||
<Compile Include="Security\Identity\FormsAuthenticationSecureDataFormat.cs" />
|
||||
<Compile Include="Security\Identity\OwinExtensions.cs" />
|
||||
<Compile Include="Security\Identity\UmbracoBackOfficeCookieAuthenticationOptions.cs" />
|
||||
<Compile Include="Scheduling\TaskAndFactoryExtensions.cs" />
|
||||
<Compile Include="SingletonUmbracoContextAccessor.cs" />
|
||||
<Compile Include="Strategies\Migrations\ClearCsrfCookiesAfterUpgrade.cs" />
|
||||
<Compile Include="Strategies\Migrations\ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs" />
|
||||
<Compile Include="Strategies\Migrations\EnsureListViewDataTypeIsCreated.cs" />
|
||||
|
||||
@@ -16,7 +16,6 @@ using SystemDirectories = Umbraco.Core.IO.SystemDirectories;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Class that encapsulates Umbraco information of a specific HTTP request
|
||||
/// </summary>
|
||||
|
||||
35
src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
Normal file
35
src/Umbraco.Web/UmbracoDefaultOwinStartup.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Owin;
|
||||
using Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Security.Identity;
|
||||
|
||||
[assembly: OwinStartup("UmbracoDefaultOwinStartup", typeof(UmbracoDefaultOwinStartup))]
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// The default way to configure OWIN for Umbraco
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The startup type is specified in appSettings under owin:appStartup
|
||||
/// </remarks>
|
||||
public class UmbracoDefaultOwinStartup
|
||||
{
|
||||
public virtual void Configuration(IAppBuilder app)
|
||||
{
|
||||
//Configure the Identity user manager for use with Umbraco Back office
|
||||
// (EXPERT: an overload accepts a custom BackOfficeUserStore implementation)
|
||||
app.ConfigureUserManagerForUmbracoBackOffice(
|
||||
ApplicationContext.Current,
|
||||
Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider());
|
||||
|
||||
//Ensure owin is configured for Umbraco back office authentication. If you have any front-end OWIN
|
||||
// cookie configuration, this must be declared after it.
|
||||
app
|
||||
.UseUmbracoBackOfficeCookieAuthentication()
|
||||
.UseUmbracoBackOfficeExternalCookieAuthentication();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,43 +166,6 @@ namespace Umbraco.Web
|
||||
RewriteToUmbracoHandler(httpContext, pcr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the request by reading the FormsAuthentication cookie and setting the
|
||||
/// context and thread principle object
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// We will set the identity, culture, etc... for any request that is:
|
||||
/// * A back office request
|
||||
/// * An installer request
|
||||
/// * A /base request (since these can be back office web service requests)
|
||||
/// </remarks>
|
||||
static void AuthenticateRequest(object sender, EventArgs e)
|
||||
{
|
||||
var app = (HttpApplication)sender;
|
||||
var http = new HttpContextWrapper(app.Context);
|
||||
|
||||
// do not process if client-side request
|
||||
if (http.Request.Url.IsClientSideRequest())
|
||||
return;
|
||||
|
||||
var req = new HttpRequestWrapper(app.Request);
|
||||
|
||||
if (ShouldAuthenticateRequest(req, UmbracoContext.Current.OriginalRequestUrl))
|
||||
{
|
||||
//TODO: Here we should have an authentication mechanism, this mechanism should be smart in the way that the ASP.Net 5 pipeline works
|
||||
// in which each registered handler will attempt to authenticate and if it fails it will just call Next() so the next handler
|
||||
// executes. If it is successful, it doesn't call next and assigns the current user/principal.
|
||||
// This might actually all be possible with ASP.Net Identity and how it is setup to work already, need to investigate.
|
||||
|
||||
var ticket = http.GetUmbracoAuthTicket();
|
||||
|
||||
http.AuthenticateCurrentRequest(ticket, ShouldIgnoreTicketRenew(UmbracoContext.Current.OriginalRequestUrl, http) == false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
@@ -590,8 +553,6 @@ namespace Umbraco.Web
|
||||
BeginRequest(new HttpContextWrapper(httpContext));
|
||||
};
|
||||
|
||||
app.AuthenticateRequest += AuthenticateRequest;
|
||||
|
||||
app.PostResolveRequestCache += (sender, e) =>
|
||||
{
|
||||
var httpContext = ((HttpApplication)sender).Context;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Helpers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
@@ -54,8 +56,9 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
AntiForgery.Validate(cookieToken, headerToken);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error(typeof(AngularAntiForgeryHelper), "Could not validate XSRF token", ex);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// A filter that is used to remove the authorization cookie for the current user when the request is successful
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used so that we can log a user out in conjunction with using other filters that modify the cookies collection.
|
||||
/// This is used so that we can log a user OUT in conjunction with using other filters that modify the cookies collection.
|
||||
/// SD: I beleive this is a bug with web api since if you modify the cookies collection on the HttpContext.Current and then
|
||||
/// use a filter to write the cookie headers, the filter seems to have no affect at all.
|
||||
/// </remarks>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
@@ -16,6 +17,20 @@ namespace Umbraco.Web.WebApi
|
||||
|
||||
public static class HttpRequestMessageExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed from the latest Microsoft.AspNet.WebApi.Owin package which we cannot use because of a later webapi dependency
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<IOwinContext> TryGetOwinContext(this HttpRequestMessage request)
|
||||
{
|
||||
var httpContext = request.TryGetHttpContext();
|
||||
return httpContext
|
||||
? Attempt.Succeed(httpContext.Result.GetOwinContext())
|
||||
: Attempt<IOwinContext>.Fail();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -42,6 +43,15 @@ namespace Umbraco.Web.WebApi
|
||||
return Request.TryGetHttpContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the current HttpContext if one exists.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected Attempt<IOwinContext> TryGetOwinContext()
|
||||
{
|
||||
return Request.TryGetOwinContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ILogger
|
||||
/// </summary>
|
||||
|
||||
@@ -51,6 +51,22 @@
|
||||
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -6,6 +6,8 @@
|
||||
<package id="Examine" version="0.1.61.2941" targetFramework="net45" />
|
||||
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
|
||||
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Core" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.0" targetFramework="net45" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
|
||||
@@ -17,9 +19,15 @@
|
||||
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.28" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.Cookies" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Owin.Security.OAuth" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
|
||||
<package id="MiniProfiler" version="2.1.0" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="UrlRewritingNet.UrlRewriter" version="2.0.60829.1" targetFramework="net40" />
|
||||
<package id="xmlrpcnet" version="2.5.0" targetFramework="net40" />
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -26,6 +26,22 @@
|
||||
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -14,6 +14,22 @@
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -4,5 +4,7 @@
|
||||
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
|
||||
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net45" />
|
||||
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -113,10 +113,17 @@
|
||||
<Reference Include="AutoMapper.Net4">
|
||||
<HintPath>..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Owin">
|
||||
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Core">
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user