Lots more work done with members mostly around passwords and membership provider - can't yet update your password but it's close
This commit is contained in:
@@ -221,12 +221,7 @@ function umbDataFormatter() {
|
||||
});
|
||||
saveModel.email = propEmail.value;
|
||||
saveModel.username = propLogin.value;
|
||||
//NOTE: This would only be set for new members!
|
||||
if (angular.isString(propPass.value)) {
|
||||
// if we are resetting or changing passwords then that data will come from the property editor and
|
||||
// it's value will be an object not just a string.
|
||||
saveModel.password = propPass.value;
|
||||
}
|
||||
saveModel.password = propPass.value;
|
||||
|
||||
return saveModel;
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
</umb-property>
|
||||
</div>
|
||||
</umb-tab>
|
||||
|
||||
</umb-tab-view>
|
||||
</umb-panel>
|
||||
</form>
|
||||
@@ -9,26 +9,40 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordCont
|
||||
hasPassword: true/false,
|
||||
requiresQuestionAnswer: true/false,
|
||||
enableReset: true/false,
|
||||
enablePasswordRetrieval: true/false,
|
||||
minPasswordLength: 10
|
||||
}
|
||||
*/
|
||||
|
||||
//set defaults if they are not available
|
||||
if (!$scope.model.config || !$scope.model.config.hasPassword) {
|
||||
if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
|
||||
$scope.model.config.hasPassword = false;
|
||||
}
|
||||
if (!$scope.model.config || !$scope.model.config.requiresQuestionAnswer) {
|
||||
if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
|
||||
$scope.model.config.enablePasswordRetrieval = true;
|
||||
}
|
||||
if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
|
||||
$scope.model.config.requiresQuestionAnswer = false;
|
||||
}
|
||||
if (!$scope.model.config || !$scope.model.config.enableReset) {
|
||||
if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
|
||||
$scope.model.config.enableReset = true;
|
||||
}
|
||||
if (!$scope.model.config || !$scope.model.config.minPasswordLength) {
|
||||
if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
|
||||
$scope.model.config.minPasswordLength = 0;
|
||||
}
|
||||
|
||||
//set the model defaults - we never get supplied a password from the server so this is ok to overwrite.
|
||||
$scope.model.value = {
|
||||
newPassword: "",
|
||||
oldPassword: null,
|
||||
reset: null,
|
||||
answer: null
|
||||
};
|
||||
//the value to compare to match passwords
|
||||
$scope.confirm = "";
|
||||
|
||||
//if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
|
||||
//with validators turned on.
|
||||
$scope.changing = !$scope.model.config.hasPassword;
|
||||
|
||||
$scope.doChange = function() {
|
||||
|
||||
@@ -3,28 +3,56 @@
|
||||
<a href="" ng-click="doChange()">Change password</a>
|
||||
</div>
|
||||
<div ng-switch-when="true">
|
||||
<div class="control-group">
|
||||
|
||||
<input type="text" name="password" ng-model="model.value" id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
required
|
||||
val-server
|
||||
ng-minlength="{{model.config.minPasswordLength}}" />
|
||||
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{model.config.minPasswordLength}} characters</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServer"></span>
|
||||
|
||||
|
||||
<div class="control-group" >
|
||||
<label class="control-label" for="resetPassword">Reset password?</label>
|
||||
<div class="controls">
|
||||
<input type="checkbox" ng-model="model.value.reset"
|
||||
id="resetPassword"
|
||||
name="resetPassword"
|
||||
val-server="resetPassword"/>
|
||||
<span class="help-inline" val-msg-for="resetPassword" val-toggle-msg="valServer"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- we need to show the old pass field when the provider cannot retreive the password -->
|
||||
<div class="control-group" ng-show="model.config.hasPassword && !model.config.enablePasswordRetrieval && !model.value.reset">
|
||||
<label class="control-label" for="password">Old password</label>
|
||||
<div class="controls">
|
||||
<input type="text" name="oldPassword" ng-model="model.value.oldPassword"
|
||||
class="input-large umb-textstring textstring"
|
||||
ng-required="!model.value.reset && model.config.hasPassword && !model.config.enablePasswordRetrieval"
|
||||
val-server="oldPassword" />
|
||||
<span class="help-inline" val-msg-for="oldPassword" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="oldPassword" val-toggle-msg="valServer"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
|
||||
<input type="text" name="confirmpassword" ng-model="model.confirm" id="{{model.alias}}"
|
||||
class="umb-editor umb-textstring textstring"
|
||||
val-compare="password" />
|
||||
|
||||
<span class="help-inline" val-msg-for="confirmpassword" val-toggle-msg="valCompare">Passwords must match</span>
|
||||
<div class="control-group" ng-show="!model.value.reset">
|
||||
<label class="control-label" for="password">New password</label>
|
||||
<div class="controls">
|
||||
<input type="text" name="password" ng-model="model.value.newPassword"
|
||||
class="input-large umb-textstring textstring"
|
||||
ng-required="!model.value.reset"
|
||||
val-server="value"
|
||||
ng-minlength="{{model.config.minPasswordLength}}" />
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="required">Required</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{model.config.minPasswordLength}} characters</span>
|
||||
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServer"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group" ng-show="!model.value.reset">
|
||||
<label class="control-label" for="password">Confirm password</label>
|
||||
<div class="controls">
|
||||
<input type="text" name="confirmpassword" ng-model="model.confirm"
|
||||
class="input-large umb-textstring textstring"
|
||||
val-compare="password" />
|
||||
|
||||
<span class="help-inline" val-msg-for="confirmpassword" val-toggle-msg="valCompare">Passwords must match</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="" ng-click="cancelChange()" ng-show="model.config.hasPassword">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
public MemberController()
|
||||
: this(UmbracoContext.Current)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Web.Editors
|
||||
//TODO: Support this
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse("Editing member with a non-umbraco membership provider is currently not supported"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,7 +119,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
{
|
||||
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
@@ -145,7 +145,7 @@ namespace Umbraco.Web.Editors
|
||||
default:
|
||||
//we don't support anything else for members
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
//If we've had problems creating/updating the user with the provider then return the error
|
||||
if (ModelState.IsValid == false)
|
||||
@@ -154,7 +154,7 @@ namespace Umbraco.Web.Editors
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
|
||||
|
||||
//save the item
|
||||
//NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password
|
||||
// so it will not get overwritten!
|
||||
@@ -199,8 +199,16 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
/// <summary>
|
||||
/// Update the membership user using the membership provider (for things like email, etc...)
|
||||
/// If a password change is detected then we'll try that too.
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <remarks>
|
||||
///
|
||||
/// YES! It is completely insane how many options you have to take into account based on the membership provider. yikes!
|
||||
///
|
||||
/// TODO: We need to update this method to return the new password if it has been reset and then show that to the UI!
|
||||
///
|
||||
/// </remarks>
|
||||
private void UpdateWithMembershipProvider(MemberSave contentItem)
|
||||
{
|
||||
//Get the member from the provider
|
||||
@@ -213,7 +221,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//ok, first thing to do is check if they've changed their email
|
||||
//TODO: When we support the other membership provider data then we'll check if any of that's changed too.
|
||||
if (contentItem.Email.Trim().InvariantEquals(membershipUser.Email))
|
||||
if (contentItem.Email.Trim().InvariantEquals(membershipUser.Email) == false)
|
||||
{
|
||||
membershipUser.Email = contentItem.Email.Trim();
|
||||
|
||||
@@ -229,6 +237,135 @@ namespace Umbraco.Web.Editors
|
||||
new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default");
|
||||
}
|
||||
}
|
||||
|
||||
//password changes ?
|
||||
if (contentItem.Password == null) return;
|
||||
|
||||
//Are we resetting the password??
|
||||
if (contentItem.Password.Reset.HasValue && contentItem.Password.Reset.Value)
|
||||
{
|
||||
if (Membership.Provider.EnablePasswordReset == false)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
else if (Membership.Provider.RequiresQuestionAndAnswer && contentItem.Password.Answer.IsNullOrWhiteSpace())
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Password reset requires a password answer", new[] {"resetPassword"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
else
|
||||
{
|
||||
//ok, we should be able to reset it
|
||||
try
|
||||
{
|
||||
var newPass = Membership.Provider.ResetPassword(
|
||||
membershipUser.UserName,
|
||||
Membership.Provider.RequiresQuestionAndAnswer ? contentItem.Password.Answer : null);
|
||||
|
||||
//TODO: How do we show this new password to the front-end ???
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WarnWithException<MemberController>("Could not reset member password", ex);
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] {"resetPassword"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (contentItem.Password.NewPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//we're not resetting it so we need to try to change it.
|
||||
|
||||
if (contentItem.Password.OldPassword.IsNullOrWhiteSpace() && Membership.Provider.EnablePasswordRetrieval == false)
|
||||
{
|
||||
//if password retrieval is not enabled but there is no old password we cannot continue
|
||||
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Password cannot be changed without the old password", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
else if (contentItem.Password.OldPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if an old password is suplied try to change it
|
||||
|
||||
try
|
||||
{
|
||||
var result = Membership.Provider.ChangePassword(membershipUser.UserName, contentItem.Password.OldPassword, contentItem.Password.NewPassword);
|
||||
if (result == false)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not change password", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WarnWithException<MemberController>("Could not change member password", ex);
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
}
|
||||
else if (Membership.Provider.EnablePasswordRetrieval == false)
|
||||
{
|
||||
//we cannot continue if we cannot get the current password
|
||||
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Password cannot be changed without the old password", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
else if (Membership.Provider.RequiresQuestionAndAnswer && contentItem.Password.Answer.IsNullOrWhiteSpace())
|
||||
{
|
||||
//if the question answer is required but there isn't one, we cannot continue
|
||||
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Password cannot be changed without the password answer", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//lets try to get the old one so we can change it
|
||||
|
||||
try
|
||||
{
|
||||
var oldPassword = Membership.Provider.GetPassword(
|
||||
membershipUser.UserName,
|
||||
Membership.Provider.RequiresQuestionAndAnswer ? contentItem.Password.Answer : null);
|
||||
|
||||
try
|
||||
{
|
||||
var result = Membership.Provider.ChangePassword(membershipUser.UserName, oldPassword, contentItem.Password.NewPassword);
|
||||
if (result == false)
|
||||
{
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not change password", new[] {"value"}),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
LogHelper.WarnWithException<MemberController>("Could not change member password", ex1);
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogHelper.WarnWithException<MemberController>("Could not retrieve member password", ex2);
|
||||
ModelState.AddPropertyError(
|
||||
new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }),
|
||||
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -247,18 +384,20 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
//TODO: I think we should detect if the Umbraco membership provider is active, if so then we'll create the member first and the provider key doesn't matter
|
||||
// but if we are using a 3rd party membership provider - then we should create our IMember first and use it's key as their provider user key!
|
||||
|
||||
|
||||
//NOTE: We are casting directly to the umbraco membership provider so we can specify the member type that we want to use!
|
||||
|
||||
|
||||
var umbracoMembershipProvider = (global::umbraco.providers.members.UmbracoMembershipProvider)Membership.Provider;
|
||||
var membershipUser = umbracoMembershipProvider.CreateUser(
|
||||
contentItem.ContentTypeAlias, contentItem.Username, contentItem.Password, contentItem.Email, "", "", true, Guid.NewGuid(), out status);
|
||||
contentItem.ContentTypeAlias, contentItem.Username,
|
||||
contentItem.Password.NewPassword,
|
||||
contentItem.Email, "", "", true, Guid.NewGuid(), out status);
|
||||
|
||||
//TODO: Localize these!
|
||||
switch (status)
|
||||
{
|
||||
case MembershipCreateStatus.Success:
|
||||
|
||||
|
||||
//Go and re-fetch the persisted item
|
||||
contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim());
|
||||
//remap the values to save
|
||||
|
||||
37
src/Umbraco.Web/Models/ContentEditing/MemberPassword.cs
Normal file
37
src/Umbraco.Web/Models/ContentEditing/MemberPassword.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the data required to set a member/user password depending on the provider installed.
|
||||
/// </summary>
|
||||
public class MemberPassword
|
||||
{
|
||||
/// <summary>
|
||||
/// The password value
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This
|
||||
/// </remarks>
|
||||
[DataMember(Name = "newPassword")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The old password - used to change a password when: EnablePasswordRetrieval = false
|
||||
/// </summary>
|
||||
[DataMember(Name = "oldPassword")]
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the password is to be reset - only valid when: EnablePasswordReset = true
|
||||
/// </summary>
|
||||
[DataMember(Name = "reset")]
|
||||
public bool? Reset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The password answer - required for reset when: RequiresQuestionAndAnswer = true
|
||||
/// </summary>
|
||||
[DataMember(Name = "answer")]
|
||||
public string Answer { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
|
||||
@@ -9,6 +10,11 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
/// </summary>
|
||||
public class MemberSave : ContentBaseItemSave<IMember>
|
||||
{
|
||||
public MemberSave()
|
||||
{
|
||||
Password = new MemberPassword();
|
||||
}
|
||||
|
||||
[DataMember(Name = "username", IsRequired = true)]
|
||||
[RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")]
|
||||
public string Username { get; set; }
|
||||
@@ -16,8 +22,8 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "email", IsRequired = true)]
|
||||
[RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataMember(Name = "password")]
|
||||
public MemberPassword Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{ "hasPassword", member.Password.IsNullOrWhiteSpace() == false },
|
||||
{ "minPasswordLength", membershipProvider.MinRequiredPasswordLength },
|
||||
{ "enableReset", membershipProvider.EnablePasswordReset },
|
||||
{ "enablePasswordRetrieval" , membershipProvider.EnablePasswordRetrieval },
|
||||
{ "requiresQuestionAnswer", membershipProvider.RequiresQuestionAndAnswer }
|
||||
//TODO: Inject the other parameters in here to change the behavior of this control - based on the membership provider settings.
|
||||
}
|
||||
|
||||
@@ -311,6 +311,7 @@
|
||||
<Compile Include="Editors\MacroController.cs" />
|
||||
<Compile Include="Editors\MembershipProviderValidationFilterAttribute.cs" />
|
||||
<Compile Include="Editors\MemberTypeController.cs" />
|
||||
<Compile Include="Models\ContentEditing\MemberPassword.cs" />
|
||||
<Compile Include="Models\ContentEditing\StyleSheet.cs" />
|
||||
<Compile Include="Editors\StylesheetController.cs" />
|
||||
<Compile Include="Models\ContentEditing\AuditLog.cs" />
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Umbraco.Web.WebApi.Binders
|
||||
}
|
||||
|
||||
//return the new member with the details filled in
|
||||
return new Member(model.Name, model.Email, model.Username, model.Password, -1, contentType);
|
||||
return new Member(model.Name, model.Email, model.Username, model.Password.NewPassword, -1, contentType);
|
||||
}
|
||||
|
||||
protected override ContentItemDto<IMember> MapFromPersisted(MemberSave model)
|
||||
|
||||
Reference in New Issue
Block a user