diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index 276f601771..5e5f2c27f7 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -6,19 +6,16 @@ namespace Umbraco.Core.Models.Identity { /// /// The login provider for the login (i.e. Facebook, Google) - /// /// string LoginProvider { get; set; } /// /// Key representing the login for the provider - /// /// string ProviderKey { get; set; } /// /// User Id for the user who owns this login - /// /// int UserId { get; set; } } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 71e0dcf4a9..0d2af2118a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -361,38 +361,8 @@ namespace Umbraco.Web.Editors user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); var userMgr = TryGetOwinContext().Result.GetBackOfficeUserManager(); - var inviteArgs = new UserInviteEventArgs( - Request.TryGetHttpContext().Result.GetCurrentRequestIpAddress(), - performingUser: Security.GetUserId().Result, - userSave); - userMgr.RaiseSendingUserInvite(inviteArgs); - - // If the event is handled then return the data - if (inviteArgs.InviteHandled) - { - // if no local user was created then map the args manually for the UI - if (inviteArgs.User == null) - { - return new UserDisplay - { - Name = userSave.Name, - Email = userSave.Email, - Username = userSave.Username - }; - } - else - { - //map the save info over onto the user - user = Mapper.Map(userSave, user); - //ensure the invited date is set - user.InvitedDate = DateTime.Now; - //Save the updated user - Services.UserService.Save(user); - return Mapper.Map(user); - } - } - - if (EmailSender.CanSendRequiredEmail == false) + + if (!EmailSender.CanSendRequiredEmail && !userMgr.HasSendingUserInviteEventHandler) { throw new HttpResponseException( Request.CreateNotificationValidationErrorResponse("No Email server is configured")); @@ -430,16 +400,48 @@ namespace Umbraco.Web.Editors //ensure the invited date is set user.InvitedDate = DateTime.Now; - //Save the updated user + //Save the updated user (which will process the user groups too) Services.UserService.Save(user); var display = Mapper.Map(user); - //send the email + var inviteArgs = new UserInviteEventArgs( + Request.TryGetHttpContext().Result.GetCurrentRequestIpAddress(), + performingUser: Security.GetUserId().Result, + userSave, + user); - await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + try + { + userMgr.RaiseSendingUserInvite(inviteArgs); + } + 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}")); + } + + // If the event is handled then no need to send the email + if (inviteArgs.InviteHandled) + { + // if no user result was created then map the minimum args manually for the UI + if (!inviteArgs.ShowUserResult) + { + display = new UserDisplay + { + Name = userSave.Name, + Email = userSave.Email, + Username = userSave.Username + }; + } + } + else + { + //send the email + await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + } display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/resendInviteHeader"), Services.TextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name })); - return display; } diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index 7564b670cb..7266e688c5 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web { foreach (var error in externalLoginErrors.Errors) { - sb.AppendFormat(@"errors.push(""{0}"");", error).AppendLine(); + sb.AppendFormat(@"errors.push(""{0}"");", error.ToSingleLine()).AppendLine(); } } diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs index b0e7125195..f91c128cde 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -634,6 +634,7 @@ namespace Umbraco.Web.Security } internal void RaiseSendingUserInvite(UserInviteEventArgs args) => OnSendingUserInvite(args); + internal bool HasSendingUserInviteEventHandler => SendingUserInvite != null; // TODO: Not sure why these are not strongly typed events?? They should be in netcore! public static event EventHandler AccountLocked; diff --git a/src/Umbraco.Web/Security/UserInviteEventArgs.cs b/src/Umbraco.Web/Security/UserInviteEventArgs.cs index 2cd0994c3c..9fb53a44c0 100644 --- a/src/Umbraco.Web/Security/UserInviteEventArgs.cs +++ b/src/Umbraco.Web/Security/UserInviteEventArgs.cs @@ -5,12 +5,16 @@ namespace Umbraco.Web.Security { public class UserInviteEventArgs : IdentityAuditEventArgs { - public UserInviteEventArgs(string ipAddress, int performingUser, UserInvite invitedUser, string comment = null) + public UserInviteEventArgs(string ipAddress, int performingUser, UserInvite invitedUser, IUser localUser, string comment = null) : base(AuditEvent.SendingUserInvite, ipAddress, comment, performingUser) { InvitedUser = invitedUser ?? throw new System.ArgumentNullException(nameof(invitedUser)); + User = localUser; } + /// + /// The model used to invite the user + /// public UserInvite InvitedUser { get; } /// @@ -19,12 +23,14 @@ namespace Umbraco.Web.Security public bool InviteHandled { get; set; } /// - /// If the event handler has created a local user then this is the result which is used to return the details to the UI + /// The local user that has been created that is pending the invite + /// + public IUser User { get; } + + /// + /// if set to true will show the edit user button in the UI, else it will not be shown /// - /// - /// It is optional to create a local user in this event. In many cases the custom invite flow will be for external logins and then local users will - /// be created via the auto-linking process. - /// - public IUser User { get; set; } + public bool ShowUserResult { get; set; } + } }