diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
index 4cc9ba402a..31ee4611d0 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs
@@ -13,6 +13,15 @@
string AuthCookieName { get; }
- string AuthCookieDomain { get; }
+ string AuthCookieDomain { get; }
+
+ ///
+ /// A boolean indicating that by default the email address will be the username
+ ///
+ ///
+ /// Even if this is true and the username is different from the email in the database, the username field will still be shown.
+ /// When this is false, the username and email fields will be shown in the user section.
+ ///
+ bool UsernameIsEmail { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs
index 1918d740ba..dc2ba7e983 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs
@@ -25,6 +25,19 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return GetOptionalTextElement("allowPasswordReset", true); }
}
+ ///
+ /// A boolean indicating that by default the email address will be the username
+ ///
+ ///
+ /// Even if this is true and the username is different from the email in the database, the username field will still be shown.
+ /// When this is false, the username and email fields will be shown in the user section.
+ ///
+ [ConfigurationProperty("usernameIsEmail")]
+ internal InnerTextConfigurationElement UsernameIsEmail
+ {
+ get { return GetOptionalTextElement("usernameIsEmail", true); }
+ }
+
[ConfigurationProperty("authCookieName")]
internal InnerTextConfigurationElement AuthCookieName
{
@@ -55,6 +68,18 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return AllowPasswordReset; }
}
+ ///
+ /// A boolean indicating that by default the email address will be the username
+ ///
+ ///
+ /// Even if this is true and the username is different from the email in the database, the username field will still be shown.
+ /// When this is false, the username and email fields will be shown in the user section.
+ ///
+ bool ISecuritySection.UsernameIsEmail
+ {
+ get { return UsernameIsEmail; }
+ }
+
string ISecuritySection.AuthCookieName
{
get { return AuthCookieName; }
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js
index cf83aa4db5..15d2353c46 100644
--- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js
@@ -15,7 +15,7 @@
vm.labels = {};
vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
- vm.emailIsUsername = true;
+ vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
//create the initial model for change password
vm.changePasswordModel = {
@@ -68,7 +68,7 @@
setUserDisplayState();
formatDatesToLocal(vm.user);
- vm.emailIsUsername = user.email === user.username;
+ vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username;
//go get the config for the membership provider and add it to the model
authResource.getMembershipProviderConfig().then(function (data) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html
index ebe493fce8..e043b50d2c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/users/user.html
+++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html
@@ -43,11 +43,11 @@
-
+
+
+
+ Required
+
+
+
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
index 6687e71f4c..4e0c24434c 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config
@@ -66,6 +66,8 @@
false
+
+ truefalse
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index 5d0779f8b6..77615cf11a 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -81,14 +81,14 @@
Domain '%0%' has been updatedEdit Current Domains
-
InheritCulture
- or inherit culture from parent nodes. Will also apply
+ or inherit culture from parent nodes. Will also apply
to the current node, unless a domain below applies too.]]>
Domains
@@ -339,11 +339,11 @@
Number of columnsNumber of rows
- Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
+ Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
by referring this ID using a <asp:content /> element.]]>
- Select a placeholder id from the list below. You can only
+ Select a placeholder id from the list below. You can only
choose Id's from the current template's master.]]>
Click on the image to see full size
@@ -380,15 +380,15 @@
- %0%' below You can add additional languages under the 'languages' in the menu on the left
+ %0%' below You can add additional languages under the 'languages' in the menu on the left
]]>
Culture NameEdit the key of the dictionary item.
-
@@ -398,6 +398,8 @@
Confirm your passwordName the %0%...Enter a name...
+ Enter an email...
+ Enter a username...Label...Enter a description...Type to search...
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
index ed0edf8b6d..70e738ffeb 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
@@ -400,6 +400,7 @@
Name the %0%...Enter a name...Enter an email...
+ Enter a username...Label...Enter a description...Type to search...
diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
index ab41246753..46f7b43e53 100644
--- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs
@@ -286,6 +286,7 @@ namespace Umbraco.Web.Editors
GetMaxRequestLength()
},
{"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn},
+ {"usernameIsEmail", UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail},
{"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')},
{"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset},
{"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage},
diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs
index cb2963124f..3ca2041ea5 100644
--- a/src/Umbraco.Web/Editors/UsersController.cs
+++ b/src/Umbraco.Web/Editors/UsersController.cs
@@ -265,13 +265,18 @@ namespace Umbraco.Web.Editors
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
-
- var existing = Services.UserService.GetByEmail(userSave.Email);
- if (existing != null)
+
+ if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
{
- ModelState.AddModelError("Email", "A user with the email already exists");
- throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
+ //ensure they are the same if we're using it
+ userSave.Username = userSave.Email;
}
+ else
+ {
+ //first validate the username if were showing it
+ CheckUniqueUsername(userSave.Username, null);
+ }
+ CheckUniqueEmail(userSave.Email, null);
//Perform authorization here to see if the current user can actually save this user with the info being requested
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
@@ -283,7 +288,7 @@ namespace Umbraco.Web.Editors
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
//format is applied without us having to duplicate that logic
- var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage);
+ var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
identityUser.Name = userSave.Name;
var created = await UserManager.CreateAsync(identityUser);
@@ -345,7 +350,7 @@ namespace Umbraco.Web.Editors
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
-
+
var hasSmtp = GlobalSettings.HasSmtpServerConfigured(RequestContext.VirtualPathRoot);
if (hasSmtp == false)
{
@@ -353,13 +358,19 @@ namespace Umbraco.Web.Editors
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
}
- var user = Services.UserService.GetByEmail(userSave.Email);
- if (user != null && (user.LastLoginDate != default(DateTime) || user.EmailConfirmedDate.HasValue))
+ IUser user;
+ if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
{
- ModelState.AddModelError("Email", "A user with the email already exists");
- throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
+ //ensure it's the same
+ userSave.Username = userSave.Email;
}
-
+ else
+ {
+ //first validate the username if we're showing it
+ user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
+ }
+ user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
+
//Perform authorization here to see if the current user can actually save this user with the info being requested
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups);
@@ -372,7 +383,7 @@ namespace Umbraco.Web.Editors
{
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
//format is applied without us having to duplicate that logic
- var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage);
+ var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
identityUser.Name = userSave.Name;
var created = await UserManager.CreateAsync(identityUser);
@@ -402,7 +413,31 @@ namespace Umbraco.Web.Editors
return display;
}
-
+
+ private IUser CheckUniqueEmail(string email, Func extraCheck)
+ {
+ var user = Services.UserService.GetByEmail(email);
+ if (user != null && (extraCheck == null || extraCheck(user)))
+ {
+ ModelState.AddModelError("Email", "A user with the email already exists");
+ throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
+ }
+ return user;
+ }
+
+ private IUser CheckUniqueUsername(string username, Func extraCheck)
+ {
+ var user = Services.UserService.GetByUsername(username);
+ if (user != null && (extraCheck == null || extraCheck(user)))
+ {
+ ModelState.AddModelError(
+ UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail ? "Email" : "Username",
+ "A user with the username already exists");
+ throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
+ }
+ return user;
+ }
+
private HttpContextBase EnsureHttpContext()
{
var attempt = this.TryGetHttpContext();
diff --git a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs
index 06895ccc68..368067814d 100644
--- a/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/UserInvite.cs
@@ -2,6 +2,8 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
+using Umbraco.Core;
+using Umbraco.Core.Configuration;
namespace Umbraco.Web.Models.ContentEditing
{
@@ -18,7 +20,10 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "email", IsRequired = true)]
[Required]
[EmailAddress]
- public string Email { get; set; }
+ public string Email { get; set; }
+
+ [DataMember(Name = "username")]
+ public string Username { get; set; }
[DataMember(Name = "message")]
public string Message { get; set; }
@@ -26,7 +31,10 @@ namespace Umbraco.Web.Models.ContentEditing
public IEnumerable Validate(ValidationContext validationContext)
{
if (UserGroups.Any() == false)
- yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" });
+ yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" });
+
+ if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail == false && Username.IsNullOrWhiteSpace())
+ yield return new ValidationResult("A username cannot be empty", new[] { "Username" });
}
}
}
\ No newline at end of file