[IEE]Reset user password

Topics: Internet/Extranet Edition
Sep 2, 2008 at 7:36 AM
Edited Sep 2, 2008 at 9:39 AM
Hi everbody,
I think I found a bug in the FBA user management. In my web.config, under membership provider,  "requiresQuestionAndAnswer" is set to true.
With this setting, if you try to reset a user password with the button, I got an error. If "requiresQuestionAndAnswer" is set to false, "reset password" button works.
I think the problem lies in OnResetPassword function in UserEdit.cs, because user.ResetPassword() needs the user answer to work, but the user answer is not provided.

This is the error log:

The parameter 'passwordAnswer' must not be empty.
Parameter name: passwordAnswer   at System.Web.Util.SecUtility.CheckParameter(String& param, Boolean checkForNull, Boolean checkIfEmpty, Boolean checkForCommas, Int32 maxSize, String paramName)
   at System.Web.Security.SqlMembershipProvider.ResetPassword(String username, String passwordAnswer)
   at CKS.FormsBasedAuthentication.UserEdit.OnResetPassword(Object sender, EventArgs e)
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


Any clue?
Thank you
Developer
Sep 2, 2008 at 9:37 AM
Hi, Check change set 16910 onwards. This has been a problem in the past and may now be fixed.

Let us know the result :)

Anthony
Sep 2, 2008 at 9:41 AM
Edited Sep 2, 2008 at 1:18 PM
Thank you! I'll give a try.

UPDATE: downloaded set 16910 to 16913, but no one works for me. "OnResetPassword" code in UserEdit.cs is the same of the older releases.

Error log:
Value cannot be null.
Parameter name: passwordAnswer   at System.Web.Util.SecUtility.CheckParameter(String& param, Boolean checkForNull, Boolean checkIfEmpty, Boolean checkForCommas, Int32 maxSize, String paramName)
   at System.Web.Security.SqlMembershipProvider.ResetPassword(String username, String passwordAnswer)
   at System.Web.Security.MembershipUser.ResetPassword(String passwordAnswer)
   at System.Web.Security.MembershipUser.ResetPassword()
   at CKS.FormsBasedAuthentication.UserEdit.OnResetPassword(Object sender, EventArgs e)
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


Thank you
Sep 2, 2008 at 4:04 PM
Edited Sep 2, 2008 at 4:06 PM
I also found this issue. Admins need the ability to reset passwords at will. Also, if an Admin creates a new user, there should be code that sends an email to the user with a link he needs to click to "complete registration" including the Q&A. This should also be true for users that self-register.

There's another bug when self-registered users are awaiting approval: The admin should have a drop-down multi-select list of all site groups to add the user into. Right now it's a blank field that does not work.

Finally, if an admin creates a user, he should have a similar drop-down multi-select list of all site groups to add the user into.
Developer
Sep 2, 2008 at 4:20 PM
I'll look into this today.  I haven't tried it with the Q&A set true, but fixed some of the issues when it was set false (which wouldn't even allow the user to be created).

As for the groups bug, that should be fixed, but they're checkboxes rather than a drop-down.  But you're right-it might need to be a multi-select if there are a lot of site groups.  If I recall, there was some configuration issue I had to complete before it worked, but I'll check that as well.  I might not have checked in that bit of code, though.  I'm in the process of checking in my own changes, and should have them done today.  I got most of them in yesterday, but now I want to get a clean build and test in my dev environment.  It took a while to get developer access to this project, and I made a lot more changes in the meantime than I usually like between checkins.


Regards,
Mike Sharp
Sep 2, 2008 at 4:26 PM

Mike,

As an end-user, I'm so happy that there's activity on FBA again! Thank You :)

Developer
Sep 2, 2008 at 5:06 PM
Actually, I'm happy too!

Unfortunately, I have bad news.  It appears that the inability of an admin to reset the password in this case is a limitation of the AspNetSqlMembership provider.  In order to be able to reset the password, even for an admin, the RequiresQuestionAndAnswer must be set to false and the EnableResetPassword set to true.  These settings allow you to lock down the membership store, leaving no security holes whatsoever.  So, the inability to reset a password in this case is by design.

However, the code should not throw an error.  About all I can do is check the state of the RequiresQuestionAndAnswer, and hide the reset button (as well as prevent the reset method from being called).

If your passwords are hashed, then they're not recoverable, because the hashing algorithm is one-way.  Unfortunately, Microsoft chose hashed as the default value for the membership provider, and I suspect most of us have left it at that.  This is the most secure (passwords are unrecoverable), but probably more secure than most applications require. 

If you have clear text or encrypted passwords, then you can recover the password, and armed with that you can change it.  There seem to be some stored-proceedure level things you can do to get the password salt, and meddle with the database directly, but this is probably not a good idea to implement in the project. 

Otherwise, I can only really think of one possible approach, which is to have a second administrators-only site also connected to the membership provider where RequiresQuestionAndAnswer is set to false.  You could administer membership there.  You might run into additional difficulties, though, unless the users were also members of the site (I don't know, because I haven't tried it).  If that was the case, you'd make sure the Members site group had no privileges at all on this site.  But I don't know for sure if this will even work.  I suspect it will, though, since I used a separate site to add the first user (me) in my membership store in the first place.

I'll fix the code so it doesn't error out, but it will be up to the implementer to configure things so the password can be reset.  Sorry!

Regards,
Mike Sharp

Developer
Sep 2, 2008 at 6:43 PM
Changeset 16942 fixes the unhandled exception, but as I mentioned, it hides the Reset Password button unless EnablePasswordReset is true and RequiresQuestionAndAnswer is false. 

I'll think about the best way to implement the reset function if passwords are clear text or encrypted, but with a one-way hash, I don't think it's going to be possible if either EnablePasswordReset is false or RequiresQuestionAndAnswer is true.  In the meantime, if anyone has any ideas/suggestions, I'm all ears!

Regards,
Mike Sharp
Sep 3, 2008 at 8:55 AM
Thank you for the answer. Keep up the good work!
Sep 5, 2008 at 6:14 PM
Mike,

I got a wsp file for FBA from Zac Smith on Aug 11. Looks like there have been many changes since then. Any chance you could share a wsp for the community? I have tried to build one myself with VS2008, but it always errors out (something about improper source control...400 errors later!). I'm simply downloading the latest FBA folder and then opening the VS project and trying to build it.

One bug I noticed with Zac's version was that users that self registered and then manually imported into the FBA Management Window could be clicked on to see their properties. Users that I made in the same window could not be clicked on to see/edit properties. Strange....

Hopefully, we'll have this not-pre-beta ;) version released soon.

Thanks,
John
Developer
Sep 5, 2008 at 7:20 PM
Hi John,

Send me an email and I'll give you the URL where I have a WSP located.  It's not the most current, but it's the one I have in production at the moment.  I've checked in a few minor bug fixes since then--mostly I added the ~sitecollection token to the UrlAction of some of the features, and there is an elevated privileges block wrapped around the code that adds a user to the default group if autoapprove is on (no membership review list).  This change may actually turn out to be unneccesary, as I'm told by Anthony that autoapprove works for him now.  But it should be enough for you to test, if you like.  I'd caution  you about putting it in production though, unless you don't make use of the blog template.  I just found out that the FBA solution messes up the blog template so that comments aren't correctly saved. http://www.codeplex.com/CKS/WorkItem/View.aspx?WorkItemId=6975

Anyway, if you want to try it out, my user name is rdcpro and the email server address is hotmail.com. 

As far as a new release goes, the latest word is that tonight Zac is re-structuring the current CKS:IEE solution, dropping the parts that aren't implemented, and it will be called CKS:FBA henceforth, since FBA is really what it's about.  At the same time, he's going to hide all but the main features, and set feature dependencies on them, which should clean things up a bit.  Then there are a few unresolved bugs which we will address, probably over the weekend and Monday or Tuesday.  At that point we're going to release a beta of the new FBA solution for folks to test.  Assuming all goes well over the next week or two, we'll get a 1.0 release out there. 

Regards,
Mike Sharp
Developer
Sep 5, 2008 at 11:21 PM
Hi!$0$0$0$0It's the privileges that fixed the autoapprove :)$0$0$0$0$0Anthony$0
Developer
Sep 5, 2008 at 11:25 PM

OMG, I should have checked that in before!  For some reason I didn't think that was the only thing that was wrong, I had just noticed it in the code and thought, well, it's gonna need elevated privileges to do this...

Mike

Developer
Sep 8, 2008 at 9:40 AM
Well, at least for me there was in fact something else wrong.

But for what it's worth, to anyone who's interested, I finally got the auto-approval working to my satisfaction.  It was driving me nuts, because everything worked, including creating the user and getting the email, except then the wizard errored out saying the user name was duplicated...even if you use a truly unique user name.  It turns out that the CreateUserWizard always calls a private AttemptCreateUser() method in the base class, which was being called after we had created out own user.  This is what was causing the error.  The email had already gone out successfully, but the UI was showing an error.

The solution was simple...put the auto-approve code in the OnCreatedUser event handler, after the base class creates the user instead of in the OnCreatingUser event handler which fires *before* the user is created.  The ApproveRequest method deletes the existing user (which is it's desired behavior when using the Membershipt Request List as well), and then creates it's own, assigning a temporary password.

Here's my updated code--it adds a new OnCreatedUser method.  This snippet includes the original OnCreatingUser method as well:

        protected override void OnCreatingUser(LoginCancelEventArgs e)
        {
            SPSite site = SPControl.GetContextSite(Context);
            if (site.Features[new Guid("{69CE2076-9A2F-4c71-AEDF-F4252C01DE4E}")] != null)
            {
                /* bms Prevent user from being added to the list multiple times if the user */
                /* is already in use.                                                       */
                if (Membership.GetUser(this.UserName) == null)
                {
                    MembershipRequest request = new MembershipRequest();
                    request.UserEmail = this.Email;
                    request.UserName = this.UserName;
                    request.PasswordQuestion = this.Question;
                    request.PasswordAnswer = this.Answer;
                    request.FirstName = this.FirstName;
                    request.LastName = this.LastName;
                    request.DefaultGroup = this._DefaultGroup;
                    MembershipRequest.CopyToReviewList(request);
                }
                this.MoveTo(this.CompleteStep);
            }
            else
            {
                base.OnCreatingUser(e);
            }
        }
        protected override void OnCreatedUser(EventArgs e)
        {
            SPSite site = SPControl.GetContextSite(Context);
            if (site.Features[new Guid("{69CE2076-9A2F-4c71-AEDF-F4252C01DE4E}")] == null)
            {
                // Note: this doesn't run using the privileges of the anonymous user, so we elevate them
                // Also, you can't use the original Site even with elevated privileges, otherwise it reverts back to anonymous.
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite site2 = new SPSite(this.Page.Request.Url.ToString()))
                    {
                        using (SPWeb web = site2.RootWeb)
                        {
                                MembershipRequest request = new MembershipRequest();
                                request.UserEmail = this.Email;
                                request.UserName = this.UserName;
                                if (System.Web.Security.Membership.RequiresQuestionAndAnswer)
                                {
                                    request.PasswordQuestion = this.Question;
                                    request.PasswordAnswer = this.Answer;
                                }
                                request.FirstName = this.FirstName;
                                request.LastName = this.LastName;
                                request.SiteName = web.Title;
                                request.SiteURL = web.Url;
                                request.ChangePasswordURL = string.Format("{0}/{1}", web.Url, web.Properties[MembershipReviewSiteURL.CHANGEPASSWORDPAGE]);
                                MembershipRequest.ApproveMembership(request, web);
                                this.MoveTo(this.CompleteStep);

                        }
                    }
                });

            }
            else
            {
                base.OnCreatedUser(e);
            }
        }



I would have checked this code in, but Zac is in the middle of re-organizing the source in preparation for the upcoming release.  I'll merge these changes in when he's done, but if you're using a changeset from 17054 or before, you'll want to add my snippet above.

If you can't compile your own, and you'd like a WSP with this change, let me know.

Regards,
Mike Sharp

Developer
Sep 8, 2008 at 9:54 AM
Sorry Mike,$0$0$0$0I was a bit overkeen there, in the whirl of combining changes I had in fact lost the same code changes, I too had created the OnCreatedUser solution for the autoapprove and managed to lose it. For some reason I did create new function called "SendApprovedMembership" though.$0$0$0$0$0Anthony$0