1- using System . Collections . Specialized ;
21using System . Data . Common ;
3- using System . Text ;
42using Microsoft . AspNetCore . Identity ;
3+ using Microsoft . Extensions . DependencyInjection ;
4+ using Microsoft . Extensions . Logging ;
55using Microsoft . Extensions . Options ;
66using Umbraco . Cms . Core ;
77using Umbraco . Cms . Core . Configuration . Models ;
8+ using Umbraco . Cms . Core . DependencyInjection ;
89using Umbraco . Cms . Core . Installer ;
910using Umbraco . Cms . Core . Models . Installer ;
1011using Umbraco . Cms . Core . Models . Membership ;
@@ -32,7 +33,9 @@ public class CreateUserStep : StepBase, IInstallStep
3233 private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator ;
3334 private readonly IMetricsConsentService _metricsConsentService ;
3435 private readonly IJsonSerializer _jsonSerializer ;
36+ private readonly ILogger < CreateUserStep > _logger ;
3537
38+ [ Obsolete ( "Please use the constructor that takes all parameters. Scheduled for removal in Umbraco 19." ) ]
3639 public CreateUserStep (
3740 IUserService userService ,
3841 DatabaseBuilder databaseBuilder ,
@@ -44,71 +47,132 @@ public CreateUserStep(
4447 IDbProviderFactoryCreator dbProviderFactoryCreator ,
4548 IMetricsConsentService metricsConsentService ,
4649 IJsonSerializer jsonSerializer )
50+ : this (
51+ userService ,
52+ databaseBuilder ,
53+ httpClientFactory ,
54+ securitySettings ,
55+ connectionStrings ,
56+ cookieManager ,
57+ userManager ,
58+ dbProviderFactoryCreator ,
59+ metricsConsentService ,
60+ jsonSerializer ,
61+ StaticServiceProvider . Instance . GetRequiredService < ILogger < CreateUserStep > > ( ) )
4762 {
48- _userService = userService ?? throw new ArgumentNullException ( nameof ( userService ) ) ;
49- _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException ( nameof ( databaseBuilder ) ) ;
63+ }
64+
65+ public CreateUserStep (
66+ IUserService userService ,
67+ DatabaseBuilder databaseBuilder ,
68+ IHttpClientFactory httpClientFactory ,
69+ IOptions < SecuritySettings > securitySettings ,
70+ IOptionsMonitor < ConnectionStrings > connectionStrings ,
71+ ICookieManager cookieManager ,
72+ IBackOfficeUserManager userManager ,
73+ IDbProviderFactoryCreator dbProviderFactoryCreator ,
74+ IMetricsConsentService metricsConsentService ,
75+ IJsonSerializer jsonSerializer ,
76+ ILogger < CreateUserStep > logger )
77+ {
78+ _userService = userService ;
79+ _databaseBuilder = databaseBuilder ;
5080 _httpClientFactory = httpClientFactory ;
51- _securitySettings = securitySettings . Value ?? throw new ArgumentNullException ( nameof ( securitySettings ) ) ;
81+ _securitySettings = securitySettings . Value ;
5282 _connectionStrings = connectionStrings ;
5383 _cookieManager = cookieManager ;
54- _userManager = userManager ?? throw new ArgumentNullException ( nameof ( userManager ) ) ;
55- _dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException ( nameof ( dbProviderFactoryCreator ) ) ;
84+ _userManager = userManager ;
85+ _dbProviderFactoryCreator = dbProviderFactoryCreator ;
5686 _metricsConsentService = metricsConsentService ;
5787 _jsonSerializer = jsonSerializer ;
88+ _logger = logger ;
5889 }
5990
6091 public async Task < Attempt < InstallationResult > > ExecuteAsync ( InstallData model )
6192 {
62- IUser ? admin = _userService . GetAsync ( Constants . Security . SuperUserKey ) . GetAwaiter ( ) . GetResult ( ) ;
63- if ( admin is null )
64- {
65- return FailWithMessage ( "Could not find the super user" ) ;
66- }
93+ IUser ? admin = await _userService . GetAsync ( Constants . Security . SuperUserKey ) ;
94+ if ( admin is null )
95+ {
96+ return FailWithMessage ( "Could not find the super user" ) ;
97+ }
6798
68- UserInstallData user = model . User ;
69- admin . Email = user . Email . Trim ( ) ;
70- admin . Name = user . Name . Trim ( ) ;
71- admin . Username = user . Email . Trim ( ) ;
99+ UserInstallData user = model . User ;
100+ admin . Email = user . Email . Trim ( ) ;
101+ admin . Name = user . Name . Trim ( ) ;
102+ admin . Username = user . Email . Trim ( ) ;
72103
73- _userService . Save ( admin ) ;
104+ _userService . Save ( admin ) ;
74105
75- BackOfficeIdentityUser ? membershipUser = await _userManager . FindByIdAsync ( Constants . Security . SuperUserIdAsString ) ;
76- if ( membershipUser == null )
77- {
78- return FailWithMessage (
79- $ "No user found in membership provider with id of { Constants . Security . SuperUserIdAsString } .") ;
80- }
106+ BackOfficeIdentityUser ? membershipUser = await _userManager . FindByIdAsync ( Constants . Security . SuperUserIdAsString ) ;
107+ if ( membershipUser == null )
108+ {
109+ return FailWithMessage (
110+ $ "No user found in membership provider with id of { Constants . Security . SuperUserIdAsString } .") ;
111+ }
81112
82- // To change the password here we actually need to reset it since we don't have an old one to use to change
83- var resetToken = await _userManager . GeneratePasswordResetTokenAsync ( membershipUser ) ;
84- if ( string . IsNullOrWhiteSpace ( resetToken ) )
113+ // To change the password here we actually need to reset it since we don't have an old one to use to change
114+ var resetToken = await _userManager . GeneratePasswordResetTokenAsync ( membershipUser ) ;
115+ if ( string . IsNullOrWhiteSpace ( resetToken ) )
116+ {
117+ return FailWithMessage ( "Could not reset password: unable to generate internal reset token" ) ;
118+ }
119+
120+ IdentityResult resetResult = await _userManager . ChangePasswordWithResetAsync ( membershipUser . Id , resetToken , user . Password . Trim ( ) ) ;
121+ if ( ! resetResult . Succeeded )
122+ {
123+ return FailWithMessage ( "Could not reset password: " + string . Join ( ", " , resetResult . Errors . ToErrorMessage ( ) ) ) ;
124+ }
125+
126+ await _metricsConsentService . SetConsentLevelAsync ( model . TelemetryLevel ) ;
127+
128+ if ( model . User . SubscribeToNewsletter )
129+ {
130+ const string EmailCollectorUrl = "https://emailcollector.umbraco.io/api/EmailProxy" ;
131+
132+ var emailModel = new EmailModel
133+ {
134+ Name = admin . Name ,
135+ Email = admin . Email ,
136+ UserGroup = [ Constants . Security . AdminGroupAlias ] ,
137+ } ;
138+
139+ HttpClient httpClient = _httpClientFactory . CreateClient ( ) ;
140+ using var content = new StringContent ( _jsonSerializer . Serialize ( emailModel ) , System . Text . Encoding . UTF8 , "application/json" ) ;
141+ try
85142 {
86- return FailWithMessage ( "Could not reset password: unable to generate internal reset token" ) ;
143+ // Set a reasonable timeout of 5 seconds for web request to save subscriber.
144+ using var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 5 ) ) ;
145+ HttpResponseMessage response = await httpClient . PostAsync ( EmailCollectorUrl , content , cts . Token ) ;
146+ if ( response . IsSuccessStatusCode )
147+ {
148+ _logger . LogInformation ( "Successfully subscribed the user created on installation to the Umbraco newsletter." ) ;
149+ }
150+ else
151+ {
152+ _logger . LogWarning ( "Failed to subscribe the user created on installation to the Umbraco newsletter. Status code: {StatusCode}" , response . StatusCode ) ;
153+ }
87154 }
88-
89- IdentityResult resetResult = await _userManager . ChangePasswordWithResetAsync ( membershipUser . Id , resetToken , user . Password . Trim ( ) ) ;
90- if ( ! resetResult . Succeeded )
155+ catch ( Exception ex )
91156 {
92- return FailWithMessage ( "Could not reset password: " + string . Join ( ", " , resetResult . Errors . ToErrorMessage ( ) ) ) ;
157+ // Log and move on if a failure occurs, we don't want to block installation for this.
158+ _logger . LogError ( ex , "Exception occurred while trying to subscribe the user created on installation to the Umbraco newsletter." ) ;
93159 }
94160
95- await _metricsConsentService . SetConsentLevelAsync ( model . TelemetryLevel ) ;
161+ }
96162
97- if ( model . User . SubscribeToNewsletter )
98- {
99- var values = new NameValueCollection { { "name" , admin . Name } , { "email" , admin . Email } } ;
100- var content = new StringContent ( _jsonSerializer . Serialize ( values ) , Encoding . UTF8 , "application/json" ) ;
163+ return Success ( ) ;
164+ }
101165
102- HttpClient httpClient = _httpClientFactory . CreateClient ( ) ;
166+ /// <summary>
167+ /// Model used to subscribe to the newsletter. Aligns with EmailModel defined in Umbraco.EmailMarketing.
168+ /// </summary>
169+ private class EmailModel
170+ {
171+ public required string Name { get ; init ; }
103172
104- try
105- {
106- HttpResponseMessage response = httpClient . PostAsync ( "https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx" , content ) . Result ;
107- }
108- catch { /* fail in silence */ }
109- }
173+ public required string Email { get ; init ; }
110174
111- return Success ( ) ;
175+ public required List < string > UserGroup { get ; init ; }
112176 }
113177
114178 /// <inheritdoc/>
0 commit comments