diff --git a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs b/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs
index a188eda8dd..1d51c45074 100644
--- a/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs
+++ b/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs
@@ -3,6 +3,7 @@
namespace Umbraco.Core.BackOffice
{
+
///
/// This class is used by events raised from the BackofficeUserManager
///
@@ -82,6 +83,7 @@ namespace Umbraco.Core.BackOffice
LogoutSuccess,
PasswordChanged,
PasswordReset,
- ResetAccessFailedCount
+ ResetAccessFailedCount,
+ SendingUserInvite
}
}
diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
index 84956c7636..090d512e87 100644
--- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs
@@ -32,6 +32,9 @@
public string UmbracoPath { get; set; } = "~/umbraco";
+ // TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for
+ // so this should not be a normal get set it has to have dynamic ability to return the correct
+ // path given UmbracoPath if this hasn't been explicitly set.
public string IconsPath { get; set; } = $"~/umbraco/assets/icons";
public string UmbracoCssPath { get; set; } = "~/css";
diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs
index 7589d506c7..24b8b20731 100644
--- a/src/Umbraco.Core/Constants-Security.cs
+++ b/src/Umbraco.Core/Constants-Security.cs
@@ -55,8 +55,6 @@
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
public const string TicketExpiresClaimType = "http://umbraco.org/2020/06/identity/claims/backoffice/ticketexpires";
- public const string BackOfficeExternalLoginOptionsProperty = "UmbracoBackOfficeExternalLoginOptions";
-
///
/// The claim type for the ASP.NET Identity security stamp
///
diff --git a/src/Umbraco.Core/IUmbracoContext.cs b/src/Umbraco.Core/IUmbracoContext.cs
index e65b7b9d80..312135169a 100644
--- a/src/Umbraco.Core/IUmbracoContext.cs
+++ b/src/Umbraco.Core/IUmbracoContext.cs
@@ -1,8 +1,8 @@
using System;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Security;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
namespace Umbraco.Web
{
diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
index feb8af24f3..cbe5b47b38 100644
--- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
+++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs
@@ -2,15 +2,6 @@
namespace Umbraco.Core.Models.Identity
{
- // TODO: Merge these in v8! This is here purely for backward compat
-
- public interface IIdentityUserLoginExtended : IIdentityUserLogin
- {
- ///
- /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider
- ///
- string UserData { get; set; }
- }
public interface IIdentityUserLogin : IEntity, IRememberBeingDirty
{
@@ -28,5 +19,10 @@ namespace Umbraco.Core.Models.Identity
/// User Id for the user who owns this login
///
int UserId { get; set; }
+
+ ///
+ /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider
+ ///
+ string UserData { get; set; }
}
}
diff --git a/src/Umbraco.Core/Models/Identity/IUserLoginInfo.cs b/src/Umbraco.Core/Models/Identity/IUserLoginInfo.cs
deleted file mode 100644
index 84dc1da7e0..0000000000
--- a/src/Umbraco.Core/Models/Identity/IUserLoginInfo.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Umbraco.Core.Models.Identity
-{
- public interface IUserLoginInfo
- {
- ///
- /// Provider for the linked login, i.e. Facebook, Google, etc.
- ///
- string LoginProvider { get; set; }
-
- /// User specific key for the login provider
- string ProviderKey { get; set; }
- }
-}
diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
index 66911b08ac..c13b28461d 100644
--- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
+++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs
@@ -7,7 +7,7 @@ namespace Umbraco.Core.Models.Identity
///
/// Entity type for a user's login (i.e. Facebook, Google)
///
- public class IdentityUserLogin : EntityBase, IIdentityUserLoginExtended
+ public class IdentityUserLogin : EntityBase, IIdentityUserLogin
{
public IdentityUserLogin(string loginProvider, string providerKey, int userId)
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
index 590a3003a7..a3455249fe 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs
@@ -6,8 +6,6 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IExternalLoginRepository : IReadWriteQueryRepository
{
- [Obsolete("Use the overload specifying IIdentityUserLoginExtended instead")]
- void SaveUserLogins(int memberId, IEnumerable logins);
void Save(int userId, IEnumerable logins);
void DeleteUserLogins(int memberId);
}
diff --git a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrors.cs b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs
similarity index 94%
rename from src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrors.cs
rename to src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs
index 39b967fa96..d7a2fed46a 100644
--- a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrors.cs
+++ b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
-namespace Umbraco.Web.Security
+namespace Umbraco.Core.Security
{
public class BackOfficeExternalLoginProviderErrors
{
diff --git a/src/Umbraco.Core/Security/IBackofficeSecurity.cs b/src/Umbraco.Core/Security/IBackofficeSecurity.cs
index 187d5d172d..4ba20f7bfa 100644
--- a/src/Umbraco.Core/Security/IBackofficeSecurity.cs
+++ b/src/Umbraco.Core/Security/IBackofficeSecurity.cs
@@ -2,7 +2,7 @@ using System;
using Umbraco.Core;
using Umbraco.Core.Models.Membership;
-namespace Umbraco.Web.Security
+namespace Umbraco.Core.Security
{
public interface IBackOfficeSecurity
{
diff --git a/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs b/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs
index 03c1035cb9..1695ecf46e 100644
--- a/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs
+++ b/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs
@@ -1,6 +1,4 @@
-using Umbraco.Web.Security;
-
-namespace Umbraco.Core.Security
+namespace Umbraco.Core.Security
{
public interface IBackOfficeSecurityAccessor
{
diff --git a/src/Umbraco.Core/Security/ValidateRequestAttempt.cs b/src/Umbraco.Core/Security/ValidateRequestAttempt.cs
index 8fab0b2533..a88e18d463 100644
--- a/src/Umbraco.Core/Security/ValidateRequestAttempt.cs
+++ b/src/Umbraco.Core/Security/ValidateRequestAttempt.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Web.Security
+namespace Umbraco.Core.Security
{
public enum ValidateRequestAttempt
{
diff --git a/src/Umbraco.Core/Services/IExternalLoginService.cs b/src/Umbraco.Core/Services/IExternalLoginService.cs
index 5f0a69eb7e..d0c2a74192 100644
--- a/src/Umbraco.Core/Services/IExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/IExternalLoginService.cs
@@ -16,9 +16,6 @@ namespace Umbraco.Core.Services
///
IEnumerable GetAll(int userId);
- [Obsolete("Use the overload specifying loginProvider and providerKey instead")]
- IEnumerable Find(IUserLoginInfo login);
-
///
/// 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 administrator has been editing/removing members
@@ -28,9 +25,6 @@ namespace Umbraco.Core.Services
///
IEnumerable Find(string loginProvider, string providerKey);
- [Obsolete("Use the Save method instead")]
- void SaveUserLogins(int userId, IEnumerable logins);
-
///
/// Saves the external logins associated with the user
///
@@ -47,7 +41,7 @@ namespace Umbraco.Core.Services
/// Save a single external login record
///
///
- void Save(IIdentityUserLoginExtended login);
+ void Save(IIdentityUserLogin login);
///
/// Deletes all user logins - normally used when a member is deleted
diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs
index 3c604a77c8..1e5cd8436e 100644
--- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs
+++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserManager.cs
@@ -8,9 +8,11 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Extensions;
using Umbraco.Net;
+using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Core.BackOffice
{
@@ -362,10 +364,15 @@ namespace Umbraco.Core.BackOffice
return result;
}
- private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername)
+ private int GetCurrentUserId(IPrincipal currentUser)
{
var umbIdentity = currentUser?.GetUmbracoIdentity();
var currentUserId = umbIdentity?.GetUserId() ?? Constants.Security.SuperUserId;
+ return currentUserId;
+ }
+ private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, int affectedUserId, string affectedUsername)
+ {
+ var currentUserId = GetCurrentUserId(currentUser);
var ip = IpResolver.GetCurrentRequestIpAddress();
return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername);
}
@@ -391,44 +398,53 @@ namespace Umbraco.Core.BackOffice
public void RaiseInvalidLoginAttemptEvent(IPrincipal currentUser, string username) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, Constants.Security.SuperUserId, username));
public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, int userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty));
- internal SignOutAuditEventArgs RaiseLogoutSuccessEvent(int userId)
- {
- var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), affectedUser: userId);
- OnLogoutSuccess(args);
- return args;
- }
public void RaiseLoginSuccessEvent(BackOfficeIdentityUser currentUser, int userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty));
- public void RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId) => OnLogoutSuccess(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty));
-
+ public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId)
+ {
+ var currentUserId = GetCurrentUserId(currentUser);
+ var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), performingUser: currentUserId, affectedUser: userId);
+ OnLogoutSuccess(args);
+ return args;
+ }
+
public void RaisePasswordChangedEvent(IPrincipal currentUser, int userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty));
public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, int userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty));
- internal void RaiseSendingUserInvite(UserInviteEventArgs args) => OnSendingUserInvite(args);
- internal bool HasSendingUserInviteEventHandler => SendingUserInvite != null;
+ public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser)
+ {
+ var currentUserId = GetCurrentUserId(currentUser);
+ var ip = IpResolver.GetCurrentRequestIpAddress();
+ var args = new UserInviteEventArgs(ip, currentUserId, invite, createdUser);
+ OnSendingUserInvite(args);
+ return args;
+ }
- // TODO: Not sure why these are not strongly typed events?? They should be in netcore!
- public static event EventHandler AccountLocked;
- public static event EventHandler AccountUnlocked;
- public static event EventHandler ForgotPasswordRequested;
- public static event EventHandler ForgotPasswordChangedSuccess;
- public static event EventHandler LoginFailed;
- public static event EventHandler LoginRequiresVerification;
- public static event EventHandler LoginSuccess;
- public static event EventHandler LogoutSuccess;
- public static event EventHandler PasswordChanged;
- public static event EventHandler PasswordReset;
- public static event EventHandler ResetAccessFailedCount;
+ public bool HasSendingUserInviteEventHandler => SendingUserInvite != null;
+
+ public static event EventHandler AccountLocked;
+ public static event EventHandler AccountUnlocked;
+ public static event EventHandler ForgotPasswordRequested;
+ public static event EventHandler ForgotPasswordChangedSuccess;
+ public static event EventHandler LoginFailed;
+ public static event EventHandler LoginRequiresVerification;
+ public static event EventHandler LoginSuccess;
+ public static event EventHandler LogoutSuccess;
+ public static event EventHandler PasswordChanged;
+ public static event EventHandler PasswordReset;
+ public static event EventHandler ResetAccessFailedCount;
///
/// Raised when a user is invited
///
- public static event EventHandler SendingUserInvite; // this event really has nothing to do with the user manager but was the most convenient place to put it
+ public static event EventHandler SendingUserInvite; // this event really has nothing to do with the user manager but was the most convenient place to put it
+
protected virtual void OnAccountLocked(IdentityAuditEventArgs e) => AccountLocked?.Invoke(this, e);
protected virtual void OnSendingUserInvite(UserInviteEventArgs e) => SendingUserInvite?.Invoke(this, e);
+
protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e) => AccountUnlocked?.Invoke(this, e);
protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e) => ForgotPasswordRequested?.Invoke(this, e);
@@ -441,7 +457,7 @@ namespace Umbraco.Core.BackOffice
protected virtual void OnLoginSuccess(IdentityAuditEventArgs e) => LoginSuccess?.Invoke(this, e);
- protected virtual void OnLogoutSuccess(IdentityAuditEventArgs e) => LogoutSuccess?.Invoke(this, e);
+ protected virtual void OnLogoutSuccess(SignOutAuditEventArgs e) => LogoutSuccess?.Invoke(this, e);
protected virtual void OnPasswordChanged(IdentityAuditEventArgs e) => PasswordChanged?.Invoke(this, e);
diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs
index b24ec73332..7ac3701c5c 100644
--- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs
+++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs
@@ -144,7 +144,7 @@ namespace Umbraco.Core.BackOffice
user.Logins.Select(x => new ExternalLogin(
x.LoginProvider,
x.ProviderKey,
- (x is IIdentityUserLoginExtended extended) ? extended.UserData : null)));
+ x.UserData)));
}
return Task.FromResult(IdentityResult.Success);
@@ -188,7 +188,7 @@ namespace Umbraco.Core.BackOffice
user.Logins.Select(x => new ExternalLogin(
x.LoginProvider,
x.ProviderKey,
- (x is IIdentityUserLoginExtended extended) ? extended.UserData : null)));
+ x.UserData)));
}
}
diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs
index cc169f31f9..ca22567418 100644
--- a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs
+++ b/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserManager.cs
@@ -3,7 +3,8 @@ using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
-
+using Umbraco.Core.Models.Membership;
+using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Core.BackOffice
{
@@ -14,6 +15,8 @@ namespace Umbraco.Core.BackOffice
public interface IBackOfficeUserManager: IDisposable
where TUser : BackOfficeIdentityUser
{
+ Task> GetLoginsAsync(TUser user);
+
Task DeleteAsync(TUser user);
Task FindByLoginAsync(string loginProvider, string providerKey);
@@ -303,8 +306,11 @@ namespace Umbraco.Core.BackOffice
void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, int userId);
void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, int userId);
- void RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId);
+ SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, int userId);
+ UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser);
void RaiseLoginSuccessEvent(TUser currentUser, int userId);
+
+ bool HasSendingUserInviteEventHandler { get; }
}
}
diff --git a/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs b/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs
deleted file mode 100644
index ab6af35519..0000000000
--- a/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Microsoft.AspNetCore.Identity;
-using Umbraco.Core.Models.Identity;
-
-namespace Umbraco.Core.BackOffice
-{
- internal class UserLoginInfoWrapper : IUserLoginInfo
- {
- private readonly UserLoginInfo _info;
-
- public static IUserLoginInfo Wrap(UserLoginInfo info) => new UserLoginInfoWrapper(info);
-
- private UserLoginInfoWrapper(UserLoginInfo info)
- {
- _info = info;
- }
-
- public string LoginProvider
- {
- get => _info.LoginProvider;
- set => _info.LoginProvider = value;
- }
-
- public string ProviderKey
- {
- get => _info.ProviderKey;
- set => _info.ProviderKey = value;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs
index 6c1af68acd..74d2fe7ff0 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/ExternalLoginFactory.cs
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Persistence.Factories
{
internal static class ExternalLoginFactory
{
- public static IIdentityUserLoginExtended BuildEntity(ExternalLoginDto dto)
+ public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto)
{
var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate)
{
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Persistence.Factories
public static ExternalLoginDto BuildDto(IIdentityUserLogin entity)
{
- var asExtended = entity as IIdentityUserLoginExtended;
var dto = new ExternalLoginDto
{
Id = entity.Id,
@@ -28,7 +27,7 @@ namespace Umbraco.Core.Persistence.Factories
LoginProvider = entity.LoginProvider,
ProviderKey = entity.ProviderKey,
UserId = entity.UserId,
- UserData = asExtended?.UserData
+ UserData = entity.UserData
};
return dto;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
index 017dbfd60b..33fd3af7fc 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
@@ -77,11 +77,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userId, i)));
}
- public void SaveUserLogins(int memberId, IEnumerable logins)
- {
- Save(memberId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
- }
-
protected override IIdentityUserLogin PerformGet(int id)
{
var sql = GetBaseQuery(false);
diff --git a/src/Umbraco.Web/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs
similarity index 83%
rename from src/Umbraco.Web/Security/SignOutAuditEventArgs.cs
rename to src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs
index e7943f70b6..961c2e6137 100644
--- a/src/Umbraco.Web/Security/SignOutAuditEventArgs.cs
+++ b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs
@@ -1,12 +1,13 @@
-namespace Umbraco.Web.Security
+namespace Umbraco.Core.BackOffice
{
+
///
/// Event args used when signing out
///
public class SignOutAuditEventArgs : IdentityAuditEventArgs
{
public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, int performingUser = -1, int affectedUser = -1)
- : base(action, ipAddress, comment, performingUser, affectedUser)
+ : base(action, ipAddress, performingUser, comment, affectedUser, null)
{
}
diff --git a/src/Umbraco.Web/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs
similarity index 88%
rename from src/Umbraco.Web/Security/UserInviteEventArgs.cs
rename to src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs
index 9fb53a44c0..4e980b7bb1 100644
--- a/src/Umbraco.Web/Security/UserInviteEventArgs.cs
+++ b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs
@@ -1,12 +1,12 @@
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Models.ContentEditing;
-namespace Umbraco.Web.Security
+namespace Umbraco.Core.BackOffice
{
public class UserInviteEventArgs : IdentityAuditEventArgs
{
public UserInviteEventArgs(string ipAddress, int performingUser, UserInvite invitedUser, IUser localUser, string comment = null)
- : base(AuditEvent.SendingUserInvite, ipAddress, comment, performingUser)
+ : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, localUser.Id, localUser.Name)
{
InvitedUser = invitedUser ?? throw new System.ArgumentNullException(nameof(invitedUser));
User = localUser;
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs
index b6254e5049..fabbfea1d4 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ExternalLoginService.cs
@@ -30,12 +30,6 @@ namespace Umbraco.Core.Services.Implement
}
}
- [Obsolete("Use the overload specifying loginProvider and providerKey instead")]
- public IEnumerable Find(IUserLoginInfo login)
- {
- return Find(login.LoginProvider, login.ProviderKey);
- }
-
///
public IEnumerable Find(string loginProvider, string providerKey)
{
@@ -47,12 +41,6 @@ namespace Umbraco.Core.Services.Implement
}
}
- [Obsolete("Use the Save method instead")]
- public void SaveUserLogins(int userId, IEnumerable logins)
- {
- Save(userId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
- }
-
///
public void Save(int userId, IEnumerable logins)
{
@@ -64,7 +52,7 @@ namespace Umbraco.Core.Services.Implement
}
///
- public void Save(IIdentityUserLoginExtended login)
+ public void Save(IIdentityUserLogin login)
{
using (var scope = ScopeProvider.CreateScope())
{
diff --git a/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs
similarity index 70%
rename from src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs
rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs
index c9ad924099..429e1953f7 100644
--- a/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs
+++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ExternalLoginServiceTests.cs
@@ -1,13 +1,12 @@
using System;
using System.Linq;
using System.Threading;
-using Microsoft.AspNet.Identity;
using NUnit.Framework;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.Legacy;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.Services;
+using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -16,15 +15,16 @@ namespace Umbraco.Tests.Services
[TestFixture]
[Apartment(ApartmentState.STA)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
- public class ExternalLoginServiceTests : TestWithDatabaseBase
+ public class ExternalLoginServiceTests : UmbracoIntegrationTest
{
- private IGlobalSettings GlobalSettings => SettingsForTests.DefaultGlobalSettings;
+ private IUserService UserService => GetRequiredService();
+ private IExternalLoginService ExternalLoginService => GetRequiredService();
[Test]
public void Removes_Existing_Duplicates_On_Save()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var providerKey = Guid.NewGuid().ToString("N");
var latest = DateTime.Now.AddDays(-1);
@@ -57,9 +57,9 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test1", providerKey)
};
- ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+ ExternalLoginService.Save(user.Id, externalLogins);
- var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList();
+ var logins = ExternalLoginService.GetAll(user.Id).ToList();
// duplicates will be removed, keeping the latest entries
Assert.AreEqual(2, logins.Count);
@@ -72,7 +72,7 @@ namespace Umbraco.Tests.Services
public void Does_Not_Persist_Duplicates()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var providerKey = Guid.NewGuid().ToString("N");
var externalLogins = new[]
@@ -81,9 +81,9 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test1", providerKey)
};
- ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+ ExternalLoginService.Save(user.Id, externalLogins);
- var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList();
+ var logins = ExternalLoginService.GetAll(user.Id).ToList();
Assert.AreEqual(1, logins.Count);
}
@@ -91,15 +91,15 @@ namespace Umbraco.Tests.Services
public void Single_Create()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id)
{
UserData = "hello"
};
- ServiceContext.ExternalLoginService.Save(extLogin);
+ ExternalLoginService.Save(extLogin);
- var found = ServiceContext.ExternalLoginService.GetAll(user.Id);
+ var found = ExternalLoginService.GetAll(user.Id);
Assert.AreEqual(1, found.Count());
Assert.IsTrue(extLogin.HasIdentity);
@@ -110,18 +110,18 @@ namespace Umbraco.Tests.Services
public void Single_Update()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id)
{
UserData = "hello"
};
- ServiceContext.ExternalLoginService.Save(extLogin);
+ ExternalLoginService.Save(extLogin);
extLogin.UserData = "world";
- ServiceContext.ExternalLoginService.Save(extLogin);
+ ExternalLoginService.Save(extLogin);
- var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList();
+ var found = ExternalLoginService.GetAll(user.Id).ToList();
Assert.AreEqual(1, found.Count);
Assert.AreEqual("world", found[0].UserData);
}
@@ -130,7 +130,7 @@ namespace Umbraco.Tests.Services
public void Multiple_Update()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var providerKey1 = Guid.NewGuid().ToString("N");
var providerKey2 = Guid.NewGuid().ToString("N");
@@ -139,16 +139,16 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test1", providerKey1, "hello"),
new ExternalLogin("test2", providerKey2, "world")
};
- ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+ ExternalLoginService.Save(user.Id, extLogins);
extLogins = new[]
{
new ExternalLogin("test1", providerKey1, "123456"),
new ExternalLogin("test2", providerKey2, "987654")
};
- ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+ ExternalLoginService.Save(user.Id, extLogins);
- var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().OrderBy(x => x.LoginProvider).ToList();
+ var found = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
Assert.AreEqual(2, found.Count);
Assert.AreEqual("123456", found[0].UserData);
Assert.AreEqual("987654", found[1].UserData);
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Services
public void Can_Find_As_Extended_Type()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var providerKey1 = Guid.NewGuid().ToString("N");
var providerKey2 = Guid.NewGuid().ToString("N");
@@ -167,11 +167,11 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test1", providerKey1, "hello"),
new ExternalLogin("test2", providerKey2, "world")
};
- ServiceContext.ExternalLoginService.Save(user.Id, extLogins);
+ ExternalLoginService.Save(user.Id, extLogins);
- var found = ServiceContext.ExternalLoginService.Find("test2", providerKey2).ToList();
+ var found = ExternalLoginService.Find("test2", providerKey2).ToList();
Assert.AreEqual(1, found.Count);
- var asExtended = found.Cast().ToList();
+ var asExtended = found.ToList();
Assert.AreEqual(1, found.Count);
}
@@ -180,7 +180,7 @@ namespace Umbraco.Tests.Services
public void Add_Logins()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var externalLogins = new[]
{
@@ -188,9 +188,9 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test2", Guid.NewGuid().ToString("N"))
};
- ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+ ExternalLoginService.Save(user.Id, externalLogins);
- var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+ var logins = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
Assert.AreEqual(2, logins.Count);
for (int i = 0; i < logins.Count; i++)
{
@@ -203,7 +203,7 @@ namespace Umbraco.Tests.Services
public void Add_Update_Delete_Logins()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var externalLogins = new[]
{
@@ -213,17 +213,17 @@ namespace Umbraco.Tests.Services
new ExternalLogin("test4", Guid.NewGuid().ToString("N"))
};
- ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+ ExternalLoginService.Save(user.Id, externalLogins);
- var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+ var logins = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
logins.RemoveAt(0); // remove the first one
logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id)); // add a new one
// save new list
- ServiceContext.ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
+ ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey)));
- var updatedLogins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
+ var updatedLogins = ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList();
Assert.AreEqual(4, updatedLogins.Count);
for (int i = 0; i < updatedLogins.Count; i++)
{
@@ -235,16 +235,16 @@ namespace Umbraco.Tests.Services
public void Add_Retrieve_User_Data()
{
var user = new User(GlobalSettings, "Test", "test@test.com", "test", "helloworldtest");
- ServiceContext.UserService.Save(user);
+ UserService.Save(user);
var externalLogins = new[]
{
new ExternalLogin("test1", Guid.NewGuid().ToString("N"), "hello world")
};
- ServiceContext.ExternalLoginService.Save(user.Id, externalLogins);
+ ExternalLoginService.Save(user.Id, externalLogins);
- var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList();
+ var logins = ExternalLoginService.GetAll(user.Id).ToList();
Assert.AreEqual("hello world", logins[0].UserData);
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs
index 8222347664..e18f5cb3f4 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs
@@ -8,9 +8,8 @@ using Microsoft.AspNetCore.Routing;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Models.Membership;
-using Umbraco.Web;
+using Umbraco.Core.Security;
using Umbraco.Web.BackOffice.Filters;
-using Umbraco.Web.Security;
namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters
{
diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs
index 2f93fa2739..c5333ea524 100644
--- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs
+++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedContentCacheTests.cs
@@ -4,20 +4,15 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
-using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Tests.Common;
-using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
-using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
-using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
namespace Umbraco.Tests.Cache.PublishedCache
{
diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs
index e1b96b9a73..52378535a0 100644
--- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs
+++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs
@@ -9,8 +9,6 @@ using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
-using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Hosting;
@@ -21,9 +19,9 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
-using Umbraco.Tests.Testing.Objects.Accessors;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Tests.Common;
+using Umbraco.Core.Security;
namespace Umbraco.Tests.PublishedContent
{
diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
index cf0285f907..ad476d49ca 100644
--- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
+++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
@@ -23,7 +24,6 @@ using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
-using Umbraco.Web.Security;
using Current = Umbraco.Web.Composing.Current;
namespace Umbraco.Tests.Scoping
diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs
index 8eb98ac655..664b00b513 100644
--- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs
@@ -14,10 +14,10 @@ using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
using Umbraco.Web.WebApi;
using Umbraco.Tests.Common;
using Umbraco.Tests.TestHelpers.Entities;
+using Umbraco.Core.Security;
namespace Umbraco.Tests.TestHelpers.ControllerTesting
{
diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
index fd832f94fa..51d323d0ff 100644
--- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
@@ -5,7 +5,6 @@ using System.Threading;
using System.Web.Routing;
using System.Xml;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -18,7 +17,6 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
-using Umbraco.Web.Security;
using Umbraco.Web.Routing;
using File = System.IO.File;
using Umbraco.Web.Composing;
@@ -32,7 +30,7 @@ using Umbraco.Persistance.SqlCe;
using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Web.WebApi;
using Umbraco.Tests.Common;
-using Umbraco.Tests.Common.Builders;
+using Umbraco.Core.Security;
namespace Umbraco.Tests.TestHelpers
{
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 59b390c37d..84404ddae9 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -167,19 +167,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -469,4 +456,4 @@
-
+
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs
index a588e09ea6..4e52617e6c 100644
--- a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs
+++ b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs
@@ -1,21 +1,16 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+using System.IO;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Moq;
using NUnit.Framework;
-using Umbraco.Core.Services;
+using Umbraco.Core.Security;
using Umbraco.Tests.Common;
using Umbraco.Tests.TestHelpers;
-using Umbraco.Tests.TestHelpers.Stubs;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
-using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
using Current = Umbraco.Web.Composing.Current;
namespace Umbraco.Tests.Web
diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
index 43dfad102d..151e6b8b42 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs
@@ -31,6 +31,7 @@ using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Security;
using Constants = Umbraco.Core.Constants;
using Microsoft.AspNetCore.Identity;
+using Umbraco.Web.Editors.Filters;
namespace Umbraco.Web.BackOffice.Controllers
{
@@ -63,6 +64,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly Core.Hosting.IHostingEnvironment _hostingEnvironment;
private readonly IRequestAccessor _requestAccessor;
private readonly LinkGenerator _linkGenerator;
+ private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions;
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
@@ -83,7 +85,8 @@ namespace Umbraco.Web.BackOffice.Controllers
ISmsSender smsSender,
Core.Hosting.IHostingEnvironment hostingEnvironment,
IRequestAccessor requestAccessor,
- LinkGenerator linkGenerator)
+ LinkGenerator linkGenerator,
+ IBackOfficeExternalLoginProviders externalAuthenticationOptions)
{
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_userManager = backOfficeUserManager;
@@ -101,6 +104,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_hostingEnvironment = hostingEnvironment;
_requestAccessor = requestAccessor;
_linkGenerator = linkGenerator;
+ _externalAuthenticationOptions = externalAuthenticationOptions;
}
///
@@ -123,6 +127,7 @@ namespace Umbraco.Web.BackOffice.Controllers
/// This will also update the security stamp for the user so it can only be used once
///
[ValidateAngularAntiForgeryToken]
+ [DenyLocalLoginAuthorization]
public async Task> PostVerifyInvite([FromQuery] int id, [FromQuery] string token)
{
if (string.IsNullOrWhiteSpace(token))
@@ -154,11 +159,29 @@ namespace Umbraco.Web.BackOffice.Controllers
[UmbracoAuthorize]
[ValidateAngularAntiForgeryToken]
- public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
+ public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel)
{
var user = await _userManager.FindByIdAsync(User.Identity.GetUserId());
if (user == null) throw new InvalidOperationException("Could not find user");
+ ExternalSignInAutoLinkOptions autoLinkOptions = null;
+ var authType = (await _signInManager.GetExternalAuthenticationSchemesAsync())
+ .FirstOrDefault(x => x.Name == unlinkLoginModel.LoginProvider);
+
+ if (authType == null)
+ {
+ _logger.LogWarning("Could not find external authentication provider registered: {LoginProvider}", unlinkLoginModel.LoginProvider);
+ }
+ else
+ {
+ autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name);
+ if (!autoLinkOptions.AllowManualLinking)
+ {
+ // If AllowManualLinking is disabled for this provider we cannot unlink
+ return BadRequest();
+ }
+ }
+
var result = await _userManager.RemoveLoginAsync(
user,
unlinkLoginModel.LoginProvider,
@@ -243,6 +266,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
[UmbracoAuthorize(redirectToUmbracoLogin: false, requireApproval: false)]
[SetAngularAntiForgeryTokens]
+ [DenyLocalLoginAuthorization]
public ActionResult GetCurrentInvitedUser()
{
var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser;
@@ -266,6 +290,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
///
[SetAngularAntiForgeryTokens]
+ [DenyLocalLoginAuthorization]
public async Task PostLogin(LoginModel loginModel)
{
// Sign the user in with username/password, this also gives a chance for developers to
@@ -332,6 +357,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
///
[SetAngularAntiForgeryTokens]
+ [DenyLocalLoginAuthorization]
public async Task PostRequestPasswordReset(RequestPasswordResetModel model)
{
// If this feature is switched off in configuration the UI will be amended to not make the request to reset password available.
@@ -546,11 +572,19 @@ namespace Umbraco.Web.BackOffice.Controllers
[ValidateAngularAntiForgeryToken]
public IActionResult PostLogout()
{
- HttpContext.SignOutAsync(Core.Constants.Security.BackOfficeAuthenticationType);
+ HttpContext.SignOutAsync(Constants.Security.BackOfficeAuthenticationType);
_logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress);
- _userManager.RaiseLogoutSuccessEvent(User, int.Parse(User.Identity.GetUserId()));
+ var userId = int.Parse(User.Identity.GetUserId());
+ var args = _userManager.RaiseLogoutSuccessEvent(User, userId);
+ if (!args.SignOutRedirectUrl.IsNullOrWhiteSpace())
+ {
+ return new ObjectResult(new
+ {
+ signOutRedirectUrl = args.SignOutRedirectUrl
+ });
+ }
return Ok();
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
index 46c5625ee1..749c1c6f07 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
@@ -31,6 +31,7 @@ using Umbraco.Web.WebAssets;
using Constants = Umbraco.Core.Constants;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
+using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
@@ -50,7 +51,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
- private readonly IExternalAuthenticationOptions _externalAuthenticationOptions;
+ private readonly IBackOfficeExternalLoginProviders _externalLogins;
public BackOfficeController(
IBackOfficeUserManager userManager,
@@ -65,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ILogger logger,
IJsonSerializer jsonSerializer,
- IExternalAuthenticationOptions externalAuthenticationOptions)
+ IBackOfficeExternalLoginProviders externalLogins)
{
_userManager = userManager;
_runtimeMinifier = runtimeMinifier;
@@ -79,7 +80,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_logger = logger;
_jsonSerializer = jsonSerializer;
- _externalAuthenticationOptions = externalAuthenticationOptions;
+ _externalLogins = externalLogins;
}
[HttpGet]
@@ -260,6 +261,9 @@ namespace Umbraco.Web.BackOffice.Controllers
}
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
+ // TODO: I believe we will have to fill in our own XsrfKey like we use to do since I think
+ // we validate against that key?
+ // see https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Editors/ChallengeResult.cs#L48
return Challenge(properties, provider);
}
@@ -275,6 +279,9 @@ namespace Umbraco.Web.BackOffice.Controllers
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(ExternalLinkLoginCallback), this.GetControllerName());
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, User.Identity.GetUserId());
+ // TODO: I believe we will have to fill in our own XsrfKey like we use to do since I think
+ // we validate against that key?
+ // see https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Editors/ChallengeResult.cs#L48
return Challenge(properties, provider);
}
@@ -305,6 +312,11 @@ namespace Umbraco.Web.BackOffice.Controllers
[HttpGet]
public async Task ExternalLinkLoginCallback()
{
+ // TODO: Do we need/want to tell it an expected xsrf.
+ // In v8 the xsrf used to be set to the user id which was verified manually, in this case I think we don't specify
+ // the key and that is up to the underlying sign in manager to set so we'd just tell it to expect the user id,
+ // the XSRF value used to be set in our ChallengeResult but now we don't have that so this needs to be set in the
+ // BackOfficeController when we issue a Challenge, see TODO notes there.
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
@@ -322,8 +334,8 @@ namespace Umbraco.Web.BackOffice.Controllers
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
- var result2 = await _userManager.AddLoginAsync(user, loginInfo);
- if (result2.Succeeded)
+ var addLoginResult = await _userManager.AddLoginAsync(user, loginInfo);
+ if (addLoginResult.Succeeded)
{
// Update any authentication tokens if login succeeded
// TODO: This is a new thing that we need to implement and because we can store data with the external login now, this is exactly
@@ -334,7 +346,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
//Add errors and redirect for it to be displayed
- TempData[ViewDataExtensions.TokenExternalSignInError] = result2.Errors;
+ TempData[ViewDataExtensions.TokenExternalSignInError] = addLoginResult.Errors;
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
@@ -352,15 +364,27 @@ namespace Umbraco.Web.BackOffice.Controllers
ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment));
- //check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
- if (ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
+ //check if there is the TempData or cookies with the any token name specified, if so, assign to view bag and render the view
+ if (ViewData.FromBase64CookieData(HttpContext, ViewDataExtensions.TokenExternalSignInError, _jsonSerializer) ||
+ ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
return defaultResponse();
//First check if there's external login info, if there's not proceed as normal
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
+
if (loginInfo == null || loginInfo.Principal == null)
{
+ // if the user is not logged in, check if there's any auto login redirects specified
+ if (!_backofficeSecurityAccessor.BackOfficeSecurity.ValidateCurrentUser())
+ {
+ var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
+ if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace())
+ {
+ return ExternalLogin(oauthRedirectAuthProvider);
+ }
+ }
+
return defaultResponse();
}
@@ -383,7 +407,7 @@ namespace Umbraco.Web.BackOffice.Controllers
}
else
{
- autoLinkOptions = _externalAuthenticationOptions.Get(authType.Name);
+ autoLinkOptions = _externalLogins.Get(authType.Name);
}
// Sign in the user with this external login provider if the user already has a login
@@ -391,12 +415,6 @@ namespace Umbraco.Web.BackOffice.Controllers
var user = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
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.
-
var shouldSignIn = true;
if (autoLinkOptions != null && autoLinkOptions.OnExternalLogin != null)
{
@@ -417,7 +435,10 @@ namespace Umbraco.Web.BackOffice.Controllers
{
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
{
- ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account" });
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ new[] { "The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account" }));
}
//Remove the cookie otherwise this message will keep appearing
@@ -440,7 +461,10 @@ namespace Umbraco.Web.BackOffice.Controllers
//we are allowing auto-linking/creating of local accounts
if (email.IsNullOrWhiteSpace())
{
- ViewData.SetExternalSignInError(new[] { $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." });
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ new[] { $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked." }));
}
else
{
@@ -448,13 +472,26 @@ namespace Umbraco.Web.BackOffice.Controllers
var autoLinkUser = await _userManager.FindByEmailAsync(email);
if (autoLinkUser != null)
{
- // TODO This will be filled out with 8.9 changes
- throw new NotImplementedException("Merge 8.9 changes in!");
+ try
+ {
+ //call the callback if one is assigned
+ autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ new[] { "Could not link login provider " + loginInfo.LoginProvider + ". " + ex.Message }));
+ return true;
+ }
+
+ await LinkUser(autoLinkUser, loginInfo);
}
else
{
var name = loginInfo.Principal?.Identity?.Name;
-
if (name.IsNullOrWhiteSpace()) throw new InvalidOperationException("The Name value cannot be null");
autoLinkUser = BackOfficeIdentityUser.CreateNew(_globalSettings, email, email, autoLinkOptions.GetUserAutoLinkCulture(_globalSettings), name);
@@ -465,40 +502,76 @@ namespace Umbraco.Web.BackOffice.Controllers
}
//call the callback if one is assigned
- autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
+ try
+ {
+ autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider);
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ new[] { "Could not link login provider " + loginInfo.LoginProvider + ". " + ex.Message }));
+ return true;
+ }
var userCreationResult = await _userManager.CreateAsync(autoLinkUser);
if (userCreationResult.Succeeded == false)
{
- ViewData.SetExternalSignInError(userCreationResult.Errors.Select(x => x.Description).ToList());
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ userCreationResult.Errors.Select(x => x.Description).ToList()));
}
else
{
- var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
- if (linkResult.Succeeded == false)
- {
- ViewData.SetExternalSignInError(linkResult.Errors.Select(x => x.Description).ToList());
-
- //If this fails, we should really delete the user since it will be in an inconsistent state!
- var deleteResult = await _userManager.DeleteAsync(autoLinkUser);
- if (deleteResult.Succeeded == false)
- {
- //DOH! ... this isn't good, combine all errors to be shown
- ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList());
- }
- }
- else
- {
- //sign in
- await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
- }
+ await LinkUser(autoLinkUser, loginInfo);
}
}
}
return true;
}
+ private async Task LinkUser(BackOfficeIdentityUser autoLinkUser, ExternalLoginInfo loginInfo)
+ {
+ var existingLogins = await _userManager.GetLoginsAsync(autoLinkUser);
+ var exists = existingLogins.FirstOrDefault(x => x.LoginProvider == loginInfo.LoginProvider && x.ProviderKey == loginInfo.ProviderKey);
+
+ // if it already exists (perhaps it was added in the AutoLink callbak) then we just continue
+ if (exists != null)
+ {
+ //sign in
+ await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
+ return;
+ }
+
+ var linkResult = await _userManager.AddLoginAsync(autoLinkUser, loginInfo);
+ if (linkResult.Succeeded)
+ {
+ //we're good! sign in
+ await _signInManager.SignInAsync(autoLinkUser, isPersistent: false);
+ return;
+ }
+
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ linkResult.Errors.Select(x => x.Description).ToList()));
+
+ //If this fails, we should really delete the user since it will be in an inconsistent state!
+ var deleteResult = await _userManager.DeleteAsync(autoLinkUser);
+ if (!deleteResult.Succeeded)
+ {
+ //DOH! ... this isn't good, combine all errors to be shown
+ ViewData.SetExternalSignInProviderErrors(
+ new BackOfficeExternalLoginProviderErrors(
+ loginInfo.LoginProvider,
+ linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList()));
+ }
+ }
+
// Used for XSRF protection when adding external logins
// TODO: This is duplicated in BackOfficeSignInManager
private const string XsrfKey = "XsrfId";
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
index 23c4dd68d8..b5d152edfb 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
@@ -19,6 +19,7 @@ using Umbraco.Web.BackOffice.Profiling;
using Umbraco.Web.BackOffice.PropertyEditors;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.Common.Attributes;
+using Umbraco.Web.Common.Security;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Models.ContentEditing;
@@ -44,7 +45,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly RuntimeSettings _runtimeSettings;
private readonly SecuritySettings _securitySettings;
private readonly IRuntimeMinifier _runtimeMinifier;
- private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
+ private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly IImageUrlGenerator _imageUrlGenerator;
private readonly PreviewRoutes _previewRoutes;
@@ -61,7 +62,7 @@ namespace Umbraco.Web.BackOffice.Controllers
IOptions runtimeSettings,
IOptions securitySettings,
IRuntimeMinifier runtimeMinifier,
- IAuthenticationSchemeProvider authenticationSchemeProvider,
+ IBackOfficeExternalLoginProviders externalLogins,
IImageUrlGenerator imageUrlGenerator,
PreviewRoutes previewRoutes)
{
@@ -77,7 +78,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_runtimeSettings = runtimeSettings.Value;
_securitySettings = securitySettings.Value;
_runtimeMinifier = runtimeMinifier;
- _authenticationSchemeProvider = authenticationSchemeProvider;
+ _externalLogins = externalLogins;
_imageUrlGenerator = imageUrlGenerator;
_previewRoutes = previewRoutes;
}
@@ -419,19 +420,11 @@ namespace Umbraco.Web.BackOffice.Controllers
"externalLogins", new Dictionary
{
{
- "providers", (await _authenticationSchemeProvider.GetAllSchemesAsync())
- // Filter only external providers
- .Where(x => !x.DisplayName.IsNullOrWhiteSpace())
- // TODO: We need to filter only back office enabled schemes.
- // Before we used to have a property bag to check, now we don't so need to investigate the easiest/best
- // way to do this. We have the type so maybe we check for a marker interface, but maybe there's another way,
- // just need to investigate.
- //.Where(p => p.Properties.ContainsKey("UmbracoBackOffice"))
+ "providers", _externalLogins.GetBackOfficeProviders()
.Select(p => new
{
- authType = p.Name, caption = p.DisplayName,
- // TODO: See above, if we need this property bag in the vars then we'll need to figure something out
- // properties = p.Properties
+ authType = p.AuthenticationType, caption = p.Name,
+ properties = p.Properties
})
.ToArray()
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs
index 7248c62c96..3086e02a31 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs
@@ -201,10 +201,10 @@ namespace Umbraco.Web.BackOffice.Controllers
}
[AppendUserModifiedHeader]
- public async Task PostSetAvatar(IList files)
+ public IActionResult PostSetAvatar(IList files)
{
//borrow the logic from the user controller
- return await UsersController.PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
+ return UsersController.PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0));
}
///
@@ -216,6 +216,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
public async Task> PostChangePassword(ChangingPasswordModel data)
{
+ // TODO: Why don't we inject this? Then we can just inject a logger
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger());
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, data, _backOfficeUserManager);
@@ -240,7 +241,15 @@ namespace Umbraco.Web.BackOffice.Controllers
public async Task> GetCurrentUserLinkedLogins()
{
var identityUser = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString());
- return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey);
+
+ // deduplicate in case there are duplicates (there shouldn't be now since we have a unique constraint on the external logins
+ // but there didn't used to be)
+ var result = new Dictionary();
+ foreach (var l in identityUser.Logins)
+ {
+ result[l.LoginProvider] = l.ProviderKey;
+ }
+ return result;
}
}
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs b/src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs
deleted file mode 100644
index 89a67d8f78..0000000000
--- a/src/Umbraco.Web.BackOffice/Controllers/DenyLocalLoginAuthorizationAttribute.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Web.Http;
-using System.Web.Http.Controllers;
-using Umbraco.Web.WebApi;
-using Umbraco.Web.Security;
-
-namespace Umbraco.Web.Editors.Filters
-{
- internal class DenyLocalLoginAuthorizationAttribute : AuthorizeAttribute
- {
- protected override bool IsAuthorized(HttpActionContext actionContext)
- {
- var owinContext = actionContext.Request.TryGetOwinContext().Result;
-
- // no authorization if any external logins deny local login
- return !owinContext.Authentication.HasDenyLocalLogin();
- }
- }
-}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
index 41b6c0a889..8bc99979ff 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs
@@ -38,13 +38,12 @@ using Umbraco.Web.Common.ActionResults;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Exceptions;
using Umbraco.Web.Editors;
-using Umbraco.Web.Models;
-using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Security;
-using Umbraco.Web.WebApi.Filters;
using Constants = Umbraco.Core.Constants;
using IUser = Umbraco.Core.Models.Membership.IUser;
using Task = System.Threading.Tasks.Task;
+using Umbraco.Net;
+using Umbraco.Web.Common.ActionsResults;
+using Umbraco.Web.Common.Security;
namespace Umbraco.Web.BackOffice.Controllers
{
@@ -72,9 +71,11 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IMediaService _mediaService;
private readonly IContentService _contentService;
private readonly GlobalSettings _globalSettings;
- private readonly IBackOfficeUserManager _backOfficeUserManager;
+ private readonly IBackOfficeUserManager _userManager;
private readonly ILoggerFactory _loggerFactory;
private readonly LinkGenerator _linkGenerator;
+ private readonly IBackOfficeExternalLoginProviders _externalLogins;
+ private readonly ILogger _logger;
public UsersController(
IMediaFileSystem mediaFileSystem,
@@ -97,7 +98,8 @@ namespace Umbraco.Web.BackOffice.Controllers
IOptions globalSettings,
IBackOfficeUserManager backOfficeUserManager,
ILoggerFactory loggerFactory,
- LinkGenerator linkGenerator)
+ LinkGenerator linkGenerator,
+ IBackOfficeExternalLoginProviders externalLogins)
{
_mediaFileSystem = mediaFileSystem;
_contentSettings = contentSettings.Value;
@@ -117,9 +119,11 @@ namespace Umbraco.Web.BackOffice.Controllers
_mediaService = mediaService;
_contentService = contentService;
_globalSettings = globalSettings.Value;
- _backOfficeUserManager = backOfficeUserManager;
+ _userManager = backOfficeUserManager;
_loggerFactory = loggerFactory;
_linkGenerator = linkGenerator;
+ _externalLogins = externalLogins;
+ _logger = _loggerFactory.CreateLogger();
}
///
@@ -137,12 +141,12 @@ namespace Umbraco.Web.BackOffice.Controllers
[AppendUserModifiedHeader("id")]
[AdminUsersAuthorize]
- public async Task PostSetAvatar(int id, IList files)
+ public IActionResult PostSetAvatar(int id, IList files)
{
- return await PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
+ return PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id);
}
- internal static async Task PostSetAvatarInternal(IList files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
+ internal static IActionResult PostSetAvatarInternal(IList files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id)
{
if (files is null)
{
@@ -375,16 +379,16 @@ namespace Umbraco.Web.BackOffice.Controllers
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
identityUser.Name = userSave.Name;
- var created = await _backOfficeUserManager.CreateAsync(identityUser);
+ var created = await _userManager.CreateAsync(identityUser);
if (created.Succeeded == false)
{
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
}
string resetPassword;
- var password = _backOfficeUserManager.GeneratePassword();
+ var password = _userManager.GeneratePassword();
- var result = await _backOfficeUserManager.AddPasswordAsync(identityUser, password);
+ var result = await _userManager.AddPasswordAsync(identityUser, password);
if (result.Succeeded == false)
{
throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
@@ -416,7 +420,7 @@ namespace Umbraco.Web.BackOffice.Controllers
///
/// This will email the user an invite and generate a token that will be validated in the email
///
- public async Task PostInviteUser(UserInvite userSave)
+ public async Task> PostInviteUser(UserInvite userSave)
{
if (userSave == null) throw new ArgumentNullException("userSave");
@@ -425,7 +429,7 @@ namespace Umbraco.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
{
- throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState);
+ return new ValidationErrorResult(ModelState);
}
IUser user;
@@ -441,12 +445,9 @@ namespace Umbraco.Web.BackOffice.Controllers
}
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
- var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager();
-
- if (!EmailSender.CanSendRequiredEmail(GlobalSettings) && !userMgr.HasSendingUserInviteEventHandler)
+ if (!EmailSender.CanSendRequiredEmail(_globalSettings) && !_userManager.HasSendingUserInviteEventHandler)
{
- throw new HttpResponseException(
- Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
+ return new ValidationErrorResult("No Email server is configured");
}
//Perform authorization here to see if the current user can actually save this user with the info being requested
@@ -454,7 +455,7 @@ namespace Umbraco.Web.BackOffice.Controllers
var canSaveUser = authHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
if (canSaveUser == false)
{
- throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result);
+ return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized);
}
if (user == null)
@@ -464,10 +465,10 @@ namespace Umbraco.Web.BackOffice.Controllers
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
identityUser.Name = userSave.Name;
- var created = await _backOfficeUserManager.CreateAsync(identityUser);
+ var created = await _userManager.CreateAsync(identityUser);
if (created.Succeeded == false)
{
- throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage());
+ return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage());
}
//now re-look the user back up
@@ -484,21 +485,16 @@ namespace Umbraco.Web.BackOffice.Controllers
_userService.Save(user);
var display = _umbracoMapper.Map(user);
- var inviteArgs = new UserInviteEventArgs(
- Request.TryGetHttpContext().Result.GetCurrentRequestIpAddress(),
- performingUser: Security.GetUserId().Result,
- userSave,
- user);
+ UserInviteEventArgs inviteArgs;
try
{
- userMgr.RaiseSendingUserInvite(inviteArgs);
+ inviteArgs = _userManager.RaiseSendingUserInvite(User, userSave, user);
}
catch (Exception ex)
{
- Logger.Error(ex, "An error occured in a custom event handler while inviting the user");
- throw new HttpResponseException(
- Request.CreateNotificationValidationErrorResponse($"An error occured inviting the user (check logs for more info): {ex.Message}"));
+ _logger.LogError(ex, "An error occured in a custom event handler while inviting the user");
+ return ValidationErrorResult.CreateNotificationValidationErrorResult($"An error occured inviting the user (check logs for more info): {ex.Message}");
}
// If the event is handled then no need to send the email
@@ -553,8 +549,8 @@ namespace Umbraco.Web.BackOffice.Controllers
private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message)
{
- var user = await _backOfficeUserManager.FindByIdAsync(((int) userDisplay.Id).ToString());
- var token = await _backOfficeUserManager.GenerateEmailConfirmationTokenAsync(user);
+ var user = await _userManager.FindByIdAsync(((int) userDisplay.Id).ToString());
+ var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var inviteToken = string.Format("{0}{1}{2}",
(int)userDisplay.Id,
@@ -625,8 +621,7 @@ namespace Umbraco.Web.BackOffice.Controllers
var hasErrors = false;
// we need to check if there's any Deny Local login providers present, if so we need to ensure that the user's email address cannot be changed
- var owinContext = Request.TryGetOwinContext().Result;
- var hasDenyLocalLogin = owinContext.Authentication.HasDenyLocalLogin();
+ var hasDenyLocalLogin = _externalLogins.HasDenyLocalLogin();
if (hasDenyLocalLogin)
{
userSave.Email = found.Email; // it cannot change, this would only happen if people are mucking around with the request
@@ -706,8 +701,9 @@ namespace Umbraco.Web.BackOffice.Controllers
throw new HttpResponseException(HttpStatusCode.NotFound);
}
+ // TODO: Why don't we inject this? Then we can just inject a logger
var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger());
- var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _backOfficeUserManager);
+ var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, changingPasswordModel, _userManager);
if (passwordChangeResult.Success)
{
@@ -792,14 +788,14 @@ namespace Umbraco.Web.BackOffice.Controllers
foreach (var u in userIds)
{
- var user = await _backOfficeUserManager.FindByIdAsync(u.ToString());
+ var user = await _userManager.FindByIdAsync(u.ToString());
if (user == null)
{
notFound.Add(u);
continue;
}
- var unlockResult = await _backOfficeUserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
+ var unlockResult = await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now);
if (unlockResult.Succeeded == false)
{
throw HttpResponseException.CreateValidationErrorResponse(
diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
index 3c6b538506..7002203d33 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
@@ -2,8 +2,8 @@ using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SixLabors.ImageSharp.Web.DependencyInjection;
+using Umbraco.Web.BackOffice.Middleware;
using Umbraco.Web.BackOffice.Routing;
-using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Extensions
{
@@ -49,6 +49,7 @@ namespace Umbraco.Extensions
app.UseUmbracoRuntimeMinification();
app.UseMiddleware();
+ app.UseMiddleware();
return app;
}
diff --git a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
index 153e309992..9828931198 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/HtmlHelperBackOfficeExtensions.cs
@@ -19,6 +19,7 @@ using Umbraco.Web.Models;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebAssets;
using Umbraco.Core;
+using Umbraco.Core.Security;
namespace Umbraco.Extensions
{
@@ -61,27 +62,20 @@ namespace Umbraco.Extensions
/// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load
///
///
- ///
+ ///
///
- public static async Task AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
- BackOfficeExternalLoginProviderErrors externalLoginErrors,
- BackOfficeSignInManager signInManager,
- IEnumerable externalLoginErrors)
+ public static Task AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
+ IBackOfficeExternalLoginProviders externalLogins,
+ BackOfficeExternalLoginProviderErrors externalLoginErrors)
{
- var providers = await signInManager.GetExternalAuthenticationSchemesAsync();
+ var providers = externalLogins.GetBackOfficeProviders();
var loginProviders = providers
- // TODO: We need to filter only back office enabled schemes.
- // Before we used to have a property bag to check, now we don't so need to investigate the easiest/best
- // way to do this. We have the type so maybe we check for a marker interface, but maybe there's another way,
- // just need to investigate.
- //.Where(p => p.Properties.ContainsKey("UmbracoBackOffice"))
.Select(p => new
{
- authType = p.Name,
- caption = p.DisplayName,
- // TODO: See above, if we need this property bag in the vars then we'll need to figure something out
- //properties = p.Properties
+ authType = p.AuthenticationType,
+ caption = p.Name,
+ properties = p.Properties
})
.ToArray();
@@ -105,13 +99,7 @@ namespace Umbraco.Extensions
sb.AppendLine(JsonConvert.SerializeObject(loginProviders));
sb.AppendLine(@"});");
- return html.Raw(sb.ToString());
- }
-
- [Obsolete("Use the other overload instead")]
- public static IHtmlString AngularValueExternalLoginInfoScript(this HtmlHelper html, IEnumerable externalLoginErrors)
- {
- return html.AngularValueExternalLoginInfoScript(new BackOfficeExternalLoginProviderErrors(string.Empty, externalLoginErrors));
+ return Task.FromResult(html.Raw(sb.ToString()));
}
///
diff --git a/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs
new file mode 100644
index 0000000000..dd41de67bd
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Extensions/HttpContextExtensions.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNetCore.Http;
+using Umbraco.Core.Security;
+
+namespace Umbraco.Extensions
+{
+ public static class HttpContextExtensions
+ {
+ public static void SetExternalLoginProviderErrors(this HttpContext httpContext, BackOfficeExternalLoginProviderErrors errors)
+ => httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] = errors;
+
+ public static BackOfficeExternalLoginProviderErrors GetExternalLoginProviderErrors(this HttpContext httpContext)
+ => httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs
index 3c2efec3a3..04adab4a27 100644
--- a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
+using Umbraco.Core.Security;
using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Filters
diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentModelValidator.cs b/src/Umbraco.Web.BackOffice/Filters/ContentModelValidator.cs
index 1ce72d8723..0d7a3a14aa 100644
--- a/src/Umbraco.Web.BackOffice/Filters/ContentModelValidator.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/ContentModelValidator.cs
@@ -1,5 +1,4 @@
-using Newtonsoft.Json;
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@@ -12,7 +11,7 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Security;
+using Umbraco.Core.Security;
namespace Umbraco.Web.BackOffice.Filters
{
diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveModelValidator.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveModelValidator.cs
index ef50f4fa74..caaee1d9e0 100644
--- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveModelValidator.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveModelValidator.cs
@@ -1,8 +1,8 @@
using Microsoft.Extensions.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Filters
{
diff --git a/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs
new file mode 100644
index 0000000000..a5d22d702d
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Filters/DenyLocalLoginAuthorizationAttribute.cs
@@ -0,0 +1,34 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using Umbraco.Web.Common.Security;
+
+namespace Umbraco.Web.Editors.Filters
+{
+ public sealed class DenyLocalLoginAuthorizationAttribute : TypeFilterAttribute
+ {
+ public DenyLocalLoginAuthorizationAttribute() : base(typeof(DenyLocalLoginFilter))
+ {
+ }
+
+ private class DenyLocalLoginFilter : IAuthorizationFilter
+ {
+ private readonly IBackOfficeExternalLoginProviders _externalLogins;
+
+ public DenyLocalLoginFilter(IBackOfficeExternalLoginProviders externalLogins)
+ {
+ _externalLogins = externalLogins;
+ }
+
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ if (_externalLogins.HasDenyLocalLogin())
+ {
+ // if there is a deny local login provider then we cannot authorize
+ context.Result = new ForbidResult();
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Filters/MediaSaveModelValidator.cs b/src/Umbraco.Web.BackOffice/Filters/MediaSaveModelValidator.cs
index 925d52e948..b398a4e401 100644
--- a/src/Umbraco.Web.BackOffice/Filters/MediaSaveModelValidator.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/MediaSaveModelValidator.cs
@@ -1,8 +1,8 @@
using Microsoft.Extensions.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Filters
{
diff --git a/src/Umbraco.Web.BackOffice/Filters/MemberSaveModelValidator.cs b/src/Umbraco.Web.BackOffice/Filters/MemberSaveModelValidator.cs
index a84b4f1c9e..275220c8b4 100644
--- a/src/Umbraco.Web.BackOffice/Filters/MemberSaveModelValidator.cs
+++ b/src/Umbraco.Web.BackOffice/Filters/MemberSaveModelValidator.cs
@@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Models;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Extensions;
using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Security;
namespace Umbraco.Web.BackOffice.Filters
{
diff --git a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrorMiddlware.cs b/src/Umbraco.Web.BackOffice/Middleware/BackOfficeExternalLoginProviderErrorMiddlware.cs
similarity index 62%
rename from src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrorMiddlware.cs
rename to src/Umbraco.Web.BackOffice/Middleware/BackOfficeExternalLoginProviderErrorMiddlware.cs
index 6e6477443b..0a58f0018b 100644
--- a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderErrorMiddlware.cs
+++ b/src/Umbraco.Web.BackOffice/Middleware/BackOfficeExternalLoginProviderErrorMiddlware.cs
@@ -1,31 +1,27 @@
using System;
-using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
-using System.Web.Mvc;
-using Microsoft.Owin;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
using Newtonsoft.Json;
using Umbraco.Core;
+using Umbraco.Extensions;
-namespace Umbraco.Web.Security
+namespace Umbraco.Web.BackOffice.Middleware
{
///
/// Used to handle errors registered by external login providers
///
///
- /// When an external login provider registers an error with during the OAuth process,
+ /// When an external login provider registers an error with during the OAuth process,
/// this middleware will detect that, store the errors into cookie data and redirect to the back office login so we can read the errors back out.
///
- internal class BackOfficeExternalLoginProviderErrorMiddlware : OwinMiddleware
+ public class BackOfficeExternalLoginProviderErrorMiddlware : IMiddleware
{
- public BackOfficeExternalLoginProviderErrorMiddlware(OwinMiddleware next) : base(next)
- {
- }
-
- public override async Task Invoke(IOwinContext context)
+ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var shortCircuit = false;
- if (!context.Request.Uri.IsClientSideRequest())
+ if (!context.Request.IsClientSideRequest())
{
// check if we have any errors registered
var errors = context.GetExternalLoginProviderErrors();
@@ -39,16 +35,16 @@ namespace Umbraco.Web.Security
{
Expires = DateTime.Now.AddMinutes(5),
HttpOnly = true,
- Secure = context.Request.IsSecure
+ Secure = context.Request.IsHttps
});
- context.Response.Redirect(context.Request.Uri.ToString());
+ context.Response.Redirect(context.Request.GetEncodedUrl());
}
}
- if (Next != null && !shortCircuit)
+ if (next != null && !shortCircuit)
{
- await Next.Invoke(context);
+ await next(context);
}
}
}
diff --git a/src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs
similarity index 97%
rename from src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs
rename to src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs
index 1854187a2a..284dbbc913 100644
--- a/src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs
+++ b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs
@@ -10,7 +10,7 @@ using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Extensions;
-namespace Umbraco.Web.BackOffice.Security
+namespace Umbraco.Web.BackOffice.Middleware
{
///
/// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the principal alongside any default authentication that takes place
@@ -55,13 +55,11 @@ namespace Umbraco.Web.BackOffice.Security
{
var backOfficeIdentity = unprotected.Principal.GetUmbracoIdentity();
if (backOfficeIdentity != null)
- {
//Ok, we've got a real ticket, now we can add this ticket's identity to the current
// Principal, this means we'll have 2 identities assigned to the principal which we can
// use to authorize the preview and allow for a back office User.
context.User.AddIdentity(backOfficeIdentity);
- }
}
}
diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
index 005a0db36e..fbd4d02732 100644
--- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
+++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
@@ -8,6 +8,7 @@ using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.BackOffice.Filters;
+using Umbraco.Web.BackOffice.Middleware;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.BackOffice.Services;
diff --git a/src/Umbraco.Web.BackOffice/Services/IconService.cs b/src/Umbraco.Web.BackOffice/Services/IconService.cs
index a29f1a3210..cfb4c820be 100644
--- a/src/Umbraco.Web.BackOffice/Services/IconService.cs
+++ b/src/Umbraco.Web.BackOffice/Services/IconService.cs
@@ -115,8 +115,9 @@ namespace Umbraco.Web.BackOffice.Services
private IEnumerable GetAllIconNames()
{
+ // TODO: See comment: https://github.com/umbraco/Umbraco-CMS/pull/8884/files#r510564185
// add icons from plugins
- var appPlugins = new DirectoryInfo(_hostingEnvironment.MapPath(Constants.SystemDirectories.AppPlugins));
+ var appPlugins = new DirectoryInfo(_hostingEnvironment.MapPathWebRoot(Constants.SystemDirectories.AppPlugins));
var pluginIcons = appPlugins.Exists == false
? new List()
: appPlugins.GetDirectories()
@@ -125,7 +126,7 @@ namespace Umbraco.Web.BackOffice.Services
.SelectMany(x => x.GetFiles("*.svg", SearchOption.TopDirectoryOnly));
// add icons from IconsPath if not already added from plugins
- var directory = new DirectoryInfo(_hostingEnvironment.MapPath($"{_globalSettings.IconsPath}/"));
+ var directory = new DirectoryInfo(_hostingEnvironment.MapPathWebRoot($"{_globalSettings.Value.IconsPath}/"));
var iconNames = directory.GetFiles("*.svg")
.Where(x => pluginIcons.Any(i => i.Name == x.Name) == false);
diff --git a/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs b/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs
index c279192221..0116c6b77a 100644
--- a/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs
+++ b/src/Umbraco.Web.Common/ActionsResults/ValidationErrorResult.cs
@@ -1,16 +1,44 @@
using System.Net;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Common.ActionsResults
{
///
- /// Custom result to return a validation error message with a 400 http response and required headers
+ /// Custom result to return a validation error message with required headers
///
+ ///
+ /// The default status code is a 400 http response
+ ///
public class ValidationErrorResult : ObjectResult
{
- public ValidationErrorResult(string errorMessage) : base(new { Message = errorMessage })
+ public static ValidationErrorResult CreateNotificationValidationErrorResult(string errorMessage)
+ {
+ var notificationModel = new SimpleNotificationModel
+ {
+ Message = errorMessage
+ };
+ notificationModel.AddErrorNotification(errorMessage, string.Empty);
+ return new ValidationErrorResult(notificationModel);
+ }
+
+ public ValidationErrorResult(object value, int statusCode) : base(value)
+ {
+ StatusCode = statusCode;
+ }
+
+ public ValidationErrorResult(object value) : this(value, StatusCodes.Status400BadRequest)
+ {
+ }
+
+ public ValidationErrorResult(string errorMessage, int statusCode) : base(new { Message = errorMessage })
+ {
+ StatusCode = statusCode;
+ }
+
+ public ValidationErrorResult(string errorMessage) : this(errorMessage, StatusCodes.Status400BadRequest)
{
- StatusCode = (int)HttpStatusCode.BadRequest;
}
public override void OnFormatting(ActionContext context)
diff --git a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs
index 9e63244fcc..655df315f8 100644
--- a/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/ViewDataExtensions.cs
@@ -1,7 +1,12 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Semver;
-
+using Umbraco.Core;
+using Umbraco.Core.Security;
+using Umbraco.Core.Serialization;
namespace Umbraco.Extensions
{
@@ -21,6 +26,49 @@ namespace Umbraco.Extensions
return true;
}
+ ///
+ /// Copies data from a request cookie to view data and then clears the cookie in the response
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This is similar to TempData but in some cases we cannot use TempData which relies on the temp data provider and session.
+ /// The cookie value can either be a simple string value
+ ///
+ ///
+ public static bool FromBase64CookieData(this ViewDataDictionary viewData, HttpContext httpContext, string cookieName, IJsonSerializer serializer)
+ {
+ var hasCookie = httpContext.Request.Cookies.ContainsKey(cookieName);
+ if (!hasCookie) return false;
+
+ // get the cookie value
+ if (!httpContext.Request.Cookies.TryGetValue(cookieName, out var cookieVal))
+ {
+ return false;
+ }
+
+ // ensure the cookie is expired (must be done after reading the value)
+ httpContext.Response.Cookies.Delete(cookieName);
+
+ if (cookieVal.IsNullOrWhiteSpace())
+ return false;
+
+ try
+ {
+ var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(System.Net.WebUtility.UrlDecode(cookieVal)));
+ // deserialize to T and store in viewdata
+ viewData[cookieName] = serializer.Deserialize(decoded);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
public static string GetUmbracoPath(this ViewDataDictionary viewData)
{
return (string)viewData[TokenUmbracoPath];
@@ -60,14 +108,24 @@ namespace Umbraco.Extensions
return (SemVersion) viewData[TokenUmbracoVersion];
}
- public static IEnumerable GetExternalSignInError(this ViewDataDictionary viewData)
+ ///
+ /// Used by the back office login screen to get any registered external login provider errors
+ ///
+ ///
+ ///
+ public static BackOfficeExternalLoginProviderErrors GetExternalSignInProviderErrors(this ViewDataDictionary viewData)
{
- return (IEnumerable)viewData[TokenExternalSignInError];
+ return (BackOfficeExternalLoginProviderErrors)viewData[TokenExternalSignInError];
}
- public static void SetExternalSignInError(this ViewDataDictionary viewData, IEnumerable value)
+ ///
+ /// Used by the back office controller to register any external login provider errors
+ ///
+ ///
+ ///
+ public static void SetExternalSignInProviderErrors(this ViewDataDictionary viewData, BackOfficeExternalLoginProviderErrors errors)
{
- viewData[TokenExternalSignInError] = value;
+ viewData[TokenExternalSignInError] = errors;
}
public static string GetPasswordResetCode(this ViewDataDictionary viewData)
diff --git a/src/Umbraco.Web.Common/Filters/UmbracoAuthorizeFilter.cs b/src/Umbraco.Web.Common/Filters/UmbracoAuthorizeFilter.cs
index 66b1462ae9..bde15b0f65 100644
--- a/src/Umbraco.Web.Common/Filters/UmbracoAuthorizeFilter.cs
+++ b/src/Umbraco.Web.Common/Filters/UmbracoAuthorizeFilter.cs
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using System;
using Umbraco.Core;
+using Umbraco.Core.Security;
using Umbraco.Extensions;
using Umbraco.Web.Security;
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
diff --git a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderOptions.cs b/src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs
similarity index 69%
rename from src/Umbraco.Web/Security/BackOfficeExternalLoginProviderOptions.cs
rename to src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs
index 4ef527460e..de16d0ec14 100644
--- a/src/Umbraco.Web/Security/BackOfficeExternalLoginProviderOptions.cs
+++ b/src/Umbraco.Web.Common/Security/BackOfficeExternalLoginProviderOptions.cs
@@ -1,10 +1,7 @@
-using Microsoft.Owin;
-using Microsoft.Owin.Security;
-using System;
-using System.Collections;
+using System;
using System.Runtime.Serialization;
-namespace Umbraco.Web.Security
+namespace Umbraco.Web.Common.Security
{
///
@@ -12,19 +9,9 @@ namespace Umbraco.Web.Security
///
public class BackOfficeExternalLoginProviderOptions
{
- ///
- /// When specified this will be called to retrieve the used during the authentication Challenge response.
- ///
- ///
- /// This will generally not be needed since OpenIdConnect.RedirectToIdentityProvider options should be used instead
- ///
- [IgnoreDataMember]
- public Func OnChallenge { get; set; }
-
///
/// Options used to control how users can be auto-linked/created/updated based on the external login provider
///
- [IgnoreDataMember] // we are ignoring this one from serialization for backwards compat since these options are manually incuded in the response separately
public ExternalSignInAutoLinkOptions AutoLinkOptions { get; set; } = new ExternalSignInAutoLinkOptions();
///
diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
index 7ef628197e..dc312ed9ca 100644
--- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
+++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs
@@ -7,21 +7,21 @@ using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
-using Umbraco.Web.Security;
namespace Umbraco.Web.Common.Security
{
- public class BackofficeSecurity : IBackOfficeSecurity
+ public class BackOfficeSecurity : IBackOfficeSecurity
{
private readonly IUserService _userService;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
- public BackofficeSecurity(
+ public BackOfficeSecurity(
IUserService userService,
IOptions globalSettings,
IHostingEnvironment hostingEnvironment,
diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs
index 4341b5524d..0a3c362971 100644
--- a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs
+++ b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs
@@ -35,7 +35,7 @@ namespace Umbraco.Web.Common.Security
{
if (_backOfficeSecurityAccessor.BackOfficeSecurity is null)
{
- _backOfficeSecurityAccessor.BackOfficeSecurity = new BackofficeSecurity(_userService, _globalSettings, _hostingEnvironment, _httpContextAccessor);
+ _backOfficeSecurityAccessor.BackOfficeSecurity = new BackOfficeSecurity(_userService, _globalSettings, _hostingEnvironment, _httpContextAccessor);
}
}
diff --git a/src/Umbraco.Web.Common/Security/ExternalAuthenticationOptions.cs b/src/Umbraco.Web.Common/Security/ExternalAuthenticationOptions.cs
deleted file mode 100644
index 9005251835..0000000000
--- a/src/Umbraco.Web.Common/Security/ExternalAuthenticationOptions.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Umbraco.Web.Common.Security
-{
- // TODO: We need to implement this and extend it to support the back office external login options
- public interface IExternalAuthenticationOptions
- {
- ExternalSignInAutoLinkOptions Get(string authenticationType);
- }
-
-}
diff --git a/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs
index d39d54ba3d..0a81a503dd 100644
--- a/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs
+++ b/src/Umbraco.Web.Common/Security/ExternalSignInAutoLinkOptions.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Identity;
using System;
+using System.Runtime.Serialization;
using Umbraco.Core.BackOffice;
using Umbraco.Core.Configuration.Models;
using SecurityConstants = Umbraco.Core.Constants.Security;
diff --git a/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs b/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs
new file mode 100644
index 0000000000..106f5378bb
--- /dev/null
+++ b/src/Umbraco.Web.Common/Security/IBackOfficeExternalLoginProviders.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Umbraco.Web.Common.Security
+{
+ // TODO: We need to implement this and extend it to support the back office external login options
+ // basically migrate things from AuthenticationManagerExtensions & AuthenticationOptionsExtensions
+ // and use this to get the back office external login infos
+ public interface IBackOfficeExternalLoginProviders
+ {
+ ExternalSignInAutoLinkOptions Get(string authenticationType);
+
+ IEnumerable GetBackOfficeProviders();
+
+ ///
+ /// Returns the authentication type for the last registered external login (oauth) provider that specifies an auto-login redirect option
+ ///
+ ///
+ ///
+ string GetAutoLoginProvider();
+
+ bool HasDenyLocalLogin();
+ }
+
+ // TODO: we'll need to register these somehow
+ public class BackOfficeExternalLoginProvider
+ {
+ public string Name { get; set; }
+ public string AuthenticationType { get; set; }
+
+ // TODO: I believe this should be replaced with just a reference to BackOfficeExternalLoginProviderOptions
+ public IReadOnlyDictionary Properties { get; set; }
+ }
+
+}
diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs
index 290af371cc..a0bc8b9140 100644
--- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs
+++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs
@@ -1,14 +1,11 @@
using System;
-using Microsoft.Extensions.Options;
-using Umbraco.Composing;
using Umbraco.Core;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Security;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
namespace Umbraco.Web
{
diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml
index e4e4c1d191..3cf342e6e0 100644
--- a/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml
+++ b/src/Umbraco.Web.UI.NetCore/umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml
@@ -8,11 +8,11 @@
@using Umbraco.Core.Hosting
@using Umbraco.Extensions
@using Umbraco.Web.BackOffice.Controllers
-@inject BackOfficeSignInManager signInManager
@inject BackOfficeServerVariables backOfficeServerVariables
@inject IUmbracoVersion umbracoVersion
@inject IHostingEnvironment hostingEnvironment
@inject IOptions globalSettings
+@inject IBackOfficeExternalLoginProviders externalLogins
@inject IRuntimeMinifier runtimeMinifier
@{
@@ -59,7 +59,7 @@
-
- @*And finally we can load in our angular app*@
-
-
-
-