Skip to content

Commit 72d9f56

Browse files
V9: Use current request for emails (#11778)
* Use request url for email * Fixed potential null ref exceptions Co-authored-by: Bjarke Berg <[email protected]>
1 parent 65c0039 commit 72d9f56

File tree

3 files changed

+160
-19
lines changed

3 files changed

+160
-19
lines changed

src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Identity;
1010
using Microsoft.AspNetCore.Mvc;
1111
using Microsoft.AspNetCore.Routing;
12+
using Microsoft.Extensions.DependencyInjection;
1213
using Microsoft.Extensions.Logging;
1314
using Microsoft.Extensions.Options;
1415
using Umbraco.Cms.Core;
@@ -29,6 +30,7 @@
2930
using Umbraco.Cms.Web.Common.Attributes;
3031
using Umbraco.Cms.Web.Common.Authorization;
3132
using Umbraco.Cms.Web.Common.Controllers;
33+
using Umbraco.Cms.Web.Common.DependencyInjection;
3234
using Umbraco.Cms.Web.Common.Filters;
3335
using Umbraco.Cms.Web.Common.Models;
3436
using Umbraco.Extensions;
@@ -71,9 +73,11 @@ public class AuthenticationController : UmbracoApiControllerBase
7173
private readonly LinkGenerator _linkGenerator;
7274
private readonly IBackOfficeExternalLoginProviders _externalAuthenticationOptions;
7375
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
76+
private readonly IHttpContextAccessor _httpContextAccessor;
77+
private readonly WebRoutingSettings _webRoutingSettings;
7478

7579
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
76-
80+
[ActivatorUtilitiesConstructor]
7781
public AuthenticationController(
7882
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
7983
IBackOfficeUserManager backOfficeUserManager,
@@ -91,7 +95,9 @@ public AuthenticationController(
9195
IHostingEnvironment hostingEnvironment,
9296
LinkGenerator linkGenerator,
9397
IBackOfficeExternalLoginProviders externalAuthenticationOptions,
94-
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions)
98+
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
99+
IHttpContextAccessor httpContextAccessor,
100+
IOptions<WebRoutingSettings> webRoutingSettings)
95101
{
96102
_backofficeSecurityAccessor = backofficeSecurityAccessor;
97103
_userManager = backOfficeUserManager;
@@ -110,6 +116,50 @@ public AuthenticationController(
110116
_linkGenerator = linkGenerator;
111117
_externalAuthenticationOptions = externalAuthenticationOptions;
112118
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
119+
_httpContextAccessor = httpContextAccessor;
120+
_webRoutingSettings = webRoutingSettings.Value;
121+
}
122+
123+
[Obsolete("Use constructor that also takes IHttpAccessor and IOptions<WebRoutingSettings>, scheduled for removal in V11")]
124+
public AuthenticationController(
125+
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
126+
IBackOfficeUserManager backOfficeUserManager,
127+
IBackOfficeSignInManager signInManager,
128+
IUserService userService,
129+
ILocalizedTextService textService,
130+
IUmbracoMapper umbracoMapper,
131+
IOptions<GlobalSettings> globalSettings,
132+
IOptions<SecuritySettings> securitySettings,
133+
ILogger<AuthenticationController> logger,
134+
IIpResolver ipResolver,
135+
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
136+
IEmailSender emailSender,
137+
ISmsSender smsSender,
138+
IHostingEnvironment hostingEnvironment,
139+
LinkGenerator linkGenerator,
140+
IBackOfficeExternalLoginProviders externalAuthenticationOptions,
141+
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions)
142+
: this(
143+
backofficeSecurityAccessor,
144+
backOfficeUserManager,
145+
signInManager,
146+
userService,
147+
textService,
148+
umbracoMapper,
149+
globalSettings,
150+
securitySettings,
151+
logger,
152+
ipResolver,
153+
passwordConfiguration,
154+
emailSender,
155+
smsSender,
156+
hostingEnvironment,
157+
linkGenerator,
158+
externalAuthenticationOptions,
159+
backOfficeTwoFactorOptions,
160+
StaticServiceProvider.Instance.GetRequiredService<IHttpContextAccessor>(),
161+
StaticServiceProvider.Instance.GetRequiredService<IOptions<WebRoutingSettings>>())
162+
{
113163
}
114164

115165
/// <summary>
@@ -629,7 +679,7 @@ private string ConstructCallbackUrl(string userId, string code)
629679
});
630680

631681
// Construct full URL using configured application URL (which will fall back to request)
632-
var applicationUri = _hostingEnvironment.ApplicationMainUrl;
682+
Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings);
633683
var callbackUri = new Uri(applicationUri, action);
634684
return callbackUri.ToString();
635685
}

src/Umbraco.Web.BackOffice/Controllers/UsersController.cs

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.AspNetCore.Http;
1414
using Microsoft.AspNetCore.Mvc;
1515
using Microsoft.AspNetCore.Routing;
16+
using Microsoft.Extensions.DependencyInjection;
1617
using Microsoft.Extensions.Logging;
1718
using Microsoft.Extensions.Options;
1819
using MimeKit;
@@ -42,6 +43,7 @@
4243
using Umbraco.Cms.Web.Common.ActionsResults;
4344
using Umbraco.Cms.Web.Common.Attributes;
4445
using Umbraco.Cms.Web.Common.Authorization;
46+
using Umbraco.Cms.Web.Common.DependencyInjection;
4547
using Umbraco.Cms.Web.Common.Security;
4648
using Umbraco.Extensions;
4749
using Constants = Umbraco.Cms.Core.Constants;
@@ -75,7 +77,10 @@ public class UsersController : BackOfficeNotificationsController
7577
private readonly UserEditorAuthorizationHelper _userEditorAuthorizationHelper;
7678
private readonly IPasswordChanger<BackOfficeIdentityUser> _passwordChanger;
7779
private readonly ILogger<UsersController> _logger;
80+
private readonly IHttpContextAccessor _httpContextAccessor;
81+
private readonly WebRoutingSettings _webRoutingSettings;
7882

83+
[ActivatorUtilitiesConstructor]
7984
public UsersController(
8085
MediaFileManager mediaFileManager,
8186
IOptions<ContentSettings> contentSettings,
@@ -96,7 +101,9 @@ public UsersController(
96101
LinkGenerator linkGenerator,
97102
IBackOfficeExternalLoginProviders externalLogins,
98103
UserEditorAuthorizationHelper userEditorAuthorizationHelper,
99-
IPasswordChanger<BackOfficeIdentityUser> passwordChanger)
104+
IPasswordChanger<BackOfficeIdentityUser> passwordChanger,
105+
IHttpContextAccessor httpContextAccessor,
106+
IOptions<WebRoutingSettings> webRoutingSettings)
100107
{
101108
_mediaFileManager = mediaFileManager;
102109
_contentSettings = contentSettings.Value;
@@ -119,6 +126,55 @@ public UsersController(
119126
_userEditorAuthorizationHelper = userEditorAuthorizationHelper;
120127
_passwordChanger = passwordChanger;
121128
_logger = _loggerFactory.CreateLogger<UsersController>();
129+
_httpContextAccessor = httpContextAccessor;
130+
_webRoutingSettings = webRoutingSettings.Value;
131+
}
132+
133+
[Obsolete("Use constructor that also takes IHttpAccessor and IOptions<WebRoutingSettings>, scheduled for removal in V11")]
134+
public UsersController(
135+
MediaFileManager mediaFileManager,
136+
IOptions<ContentSettings> contentSettings,
137+
IHostingEnvironment hostingEnvironment,
138+
ISqlContext sqlContext,
139+
IImageUrlGenerator imageUrlGenerator,
140+
IOptions<SecuritySettings> securitySettings,
141+
IEmailSender emailSender,
142+
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
143+
AppCaches appCaches,
144+
IShortStringHelper shortStringHelper,
145+
IUserService userService,
146+
ILocalizedTextService localizedTextService,
147+
IUmbracoMapper umbracoMapper,
148+
IOptions<GlobalSettings> globalSettings,
149+
IBackOfficeUserManager backOfficeUserManager,
150+
ILoggerFactory loggerFactory,
151+
LinkGenerator linkGenerator,
152+
IBackOfficeExternalLoginProviders externalLogins,
153+
UserEditorAuthorizationHelper userEditorAuthorizationHelper,
154+
IPasswordChanger<BackOfficeIdentityUser> passwordChanger)
155+
: this(mediaFileManager,
156+
contentSettings,
157+
hostingEnvironment,
158+
sqlContext,
159+
imageUrlGenerator,
160+
securitySettings,
161+
emailSender,
162+
backofficeSecurityAccessor,
163+
appCaches,
164+
shortStringHelper,
165+
userService,
166+
localizedTextService,
167+
umbracoMapper,
168+
globalSettings,
169+
backOfficeUserManager,
170+
loggerFactory,
171+
linkGenerator,
172+
externalLogins,
173+
userEditorAuthorizationHelper,
174+
passwordChanger,
175+
StaticServiceProvider.Instance.GetRequiredService<IHttpContextAccessor>(),
176+
StaticServiceProvider.Instance.GetRequiredService<IOptions<WebRoutingSettings>>())
177+
{
122178
}
123179

124180
/// <summary>
@@ -421,20 +477,25 @@ public async Task<ActionResult<UserDisplay>> PostCreateUser(UserInvite userSave)
421477
/// </remarks>
422478
public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
423479
{
424-
if (userSave == null) throw new ArgumentNullException("userSave");
480+
if (userSave == null)
481+
{
482+
throw new ArgumentNullException("userSave");
483+
}
425484

426485
if (userSave.Message.IsNullOrWhiteSpace())
486+
{
427487
ModelState.AddModelError("Message", "Message cannot be empty");
488+
}
428489

429490
IUser user;
430491
if (_securitySettings.UsernameIsEmail)
431492
{
432-
//ensure it's the same
493+
// ensure it's the same
433494
userSave.Username = userSave.Email;
434495
}
435496
else
436497
{
437-
//first validate the username if we're showing it
498+
// first validate the username if we're showing it
438499
var userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
439500
if (!(userResult.Result is null))
440501
{
@@ -443,6 +504,7 @@ public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
443504

444505
user = userResult.Value;
445506
}
507+
446508
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue);
447509

448510
if (ModelState.IsValid == false)
@@ -455,7 +517,7 @@ public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
455517
return ValidationProblem("No Email server is configured");
456518
}
457519

458-
//Perform authorization here to see if the current user can actually save this user with the info being requested
520+
// Perform authorization here to see if the current user can actually save this user with the info being requested
459521
var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, user, null, null, userSave.UserGroups);
460522
if (canSaveUser == false)
461523
{
@@ -464,8 +526,8 @@ public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
464526

465527
if (user == null)
466528
{
467-
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
468-
//format is applied without us having to duplicate that logic
529+
// we want to create the user with the UserManager, this ensures the 'empty' (special) password
530+
// format is applied without us having to duplicate that logic
469531
var identityUser = BackOfficeIdentityUser.CreateNew(_globalSettings, userSave.Username, userSave.Email, _globalSettings.DefaultUILanguage);
470532
identityUser.Name = userSave.Name;
471533

@@ -475,21 +537,21 @@ public async Task<ActionResult<UserDisplay>> PostInviteUser(UserInvite userSave)
475537
return ValidationProblem(created.Errors.ToErrorMessage());
476538
}
477539

478-
//now re-look the user back up
540+
// now re-look the user back up
479541
user = _userService.GetByEmail(userSave.Email);
480542
}
481543

482-
//map the save info over onto the user
544+
// map the save info over onto the user
483545
user = _umbracoMapper.Map(userSave, user);
484546

485-
//ensure the invited date is set
547+
// ensure the invited date is set
486548
user.InvitedDate = DateTime.Now;
487549

488-
//Save the updated user (which will process the user groups too)
550+
// Save the updated user (which will process the user groups too)
489551
_userService.Save(user);
490552
var display = _umbracoMapper.Map<UserDisplay>(user);
491553

492-
//send the email
554+
// send the email
493555
await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message);
494556

495557
display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles","resendInviteHeader"), _localizedTextService.Localize("speechBubbles","resendInviteSuccess", new[] { user.Name }));
@@ -544,14 +606,14 @@ private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from,
544606
});
545607

546608
// Construct full URL using configured application URL (which will fall back to request)
547-
var applicationUri = _hostingEnvironment.ApplicationMainUrl;
609+
Uri applicationUri = _httpContextAccessor.GetRequiredHttpContext().Request.GetApplicationUri(_webRoutingSettings);
548610
var inviteUri = new Uri(applicationUri, action);
549611

550612
var emailSubject = _localizedTextService.Localize("user","inviteEmailCopySubject",
551-
//Ensure the culture of the found user is used for the email!
613+
// Ensure the culture of the found user is used for the email!
552614
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings));
553615
var emailBody = _localizedTextService.Localize("user","inviteEmailCopyFormat",
554-
//Ensure the culture of the found user is used for the email!
616+
// Ensure the culture of the found user is used for the email!
555617
UmbracoUserExtensions.GetUserCulture(to.Language, _localizedTextService, _globalSettings),
556618
new[] { userDisplay.Name, from, message, inviteUri.ToString(), senderEmail });
557619

src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Net;
34
using System.Text;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Http.Extensions;
68
using Microsoft.Extensions.DependencyInjection;
9+
using Umbraco.Cms.Core.Configuration.Models;
710
using Umbraco.Cms.Core.Routing;
811

912
namespace Umbraco.Extensions
@@ -107,5 +110,31 @@ public static async Task<string> GetRawBodyStringAsync(this HttpRequest request,
107110
return result;
108111
}
109112
}
113+
114+
/// <summary>
115+
/// Gets the application URI, will use the one specified in settings if present
116+
/// </summary>
117+
public static Uri GetApplicationUri(this HttpRequest request, WebRoutingSettings routingSettings)
118+
{
119+
if (request == null)
120+
{
121+
throw new ArgumentNullException(nameof(request));
122+
}
123+
124+
if (routingSettings == null)
125+
{
126+
throw new ArgumentNullException(nameof(routingSettings));
127+
}
128+
129+
if (string.IsNullOrEmpty(routingSettings.UmbracoApplicationUrl))
130+
{
131+
var requestUri = new Uri(request.GetDisplayUrl());
132+
133+
// Create a new URI with the relative uri as /, this ensures that only the base path is returned.
134+
return new Uri(requestUri, "/");
135+
}
136+
137+
return new Uri(routingSettings.UmbracoApplicationUrl);
138+
}
110139
}
111140
}

0 commit comments

Comments
 (0)