FBA Management : auto-approving ?

Topics: Internet/Extranet Edition
Aug 27, 2008 at 10:03 PM
Hi and thanks for the work done !

I'm installing and testing the FBA management tool. It's difficult, some bugs or unfriendly behaviours left, but we can say that it globally works...

I would like to know if there is a possibility to auto-approve each membership request ? Means that the default status would be "approved" instead of "pending". That would be the best behaviour for our internet site (we don't want an administrator to check membership request list 2 times a day).
I looked for an option which would have done that, but didn't find. Did I miss something, or did it not exist ?

Thanks !

Developer
Aug 28, 2008 at 5:41 AM
If you deactivate the Membership Review List feature, it's supposed to auto-approve.  There is a bug in the source, though, that you'll  have to fix.  It's been mentioned recently in this discussion list, and I think it's in the issue tracker (with the fix for it).  If you can't get it to work, let me know.  I haven't tested on my dev server yet, as in my case we want to approve all requests. 

Regards,
Mike Sharp
Aug 28, 2008 at 3:44 PM
Edited Aug 28, 2008 at 3:45 PM
Thanks for your quick answer !

I found the issue tracker you talk about : http://www.codeplex.com/CKS/WorkItem/View.aspx?WorkItemId=6470
First I tested to deactivate MembershipReviewList without changing anything else, to be sure the source I have (15235) wasn't already corrected. Membership Request was broken...
So, I'm not .NET developper, but I managed to simply replace the code fixed, rebuild and redeploy. Now Membership Request works, and the user is automatically added, I can see him in User FBA Management.

But, there is still a problem...
When I was working with Membership Request List activated, and manual approval, emails was all correctly sent (pending and approved, mainly). But now, no email is sent anymore.  :o(
Is there something to do in addition ?

Thanks !
Developer
Aug 28, 2008 at 5:21 PM

Hmmm....you're right, the emails don't seem to be going out any more.  I'm working on a few other changes today myself; I'll look into that and post back with what I find.  At the moment we're approving all membership requests, but I suspect that will get old after a while, and my client is going to want it to auto-approve anyway.  So, I'm going to need this to work too.

Regards,

Mike Sharp

Developer
Aug 28, 2008 at 5:37 PM
Edited Aug 28, 2008 at 5:39 PM
Bummer.  I looked in the code and this functionality is not yet implemented.  That's what I get for assuming.  ;^) 

Checking the release note, to make sure I wasn't imagining things, it does say it's supposed to do it:

4. Administering Users
New Membership Request
When a user fills out the registration information in the registration web part that information is stored in the Membership Request List. The administrator can then edit the user information by going to Site Settings and under Users and Permissions click on "FBA Membership Request Management". When their status goes from Pending to Approved an email will be sent that will notify the user that they have been approved and will also contain a temporary password for them to use to log into the site. If you deactivate the Membership Request List feature, then all registrants are automatically added to the site and the email goes out immediately.


Personally I think it should be a configuration item like an "Auto-approve membership requests" checkbox in the FBASiteConfiguration page.  That way the item is added to the review list anyway, and the ItemAdded event checks the configuration and sets the item's approval status to approved, firing the ItemUpdated event.  

I'll try to get this done today or tomorrow, and I'll post my code changes here.  It doesn't seem like a big deal to fix, actually.

Regards,
Mike Sharp 
Aug 28, 2008 at 6:06 PM
Thanks for your answer. I'm looking in the code too, and if I understand well, you're right, no code processes email sending. Maybe I'm wrong, but it seems that no password is set to the new user, too.
I was thinking about a solution : from OnCreatingUser() (function fixed in the issue tracker), directly call ApproveMembership(). But as I say, I don't have skills in SP dev, so it could be an error ; and your solution seems better...  ;o)

Thanks for your help !
Developer
Aug 28, 2008 at 6:46 PM
Oh wait, you're right.  I looked for instances of MembershipList.MEMBERSHIPREVIEWLIST but in the OnCreatingUser event handler it uses the GUID of the list.  It does in fact create the user in the membership store, and even adds them to the right group.  That must mean a temporary password was generated by ASP.NET, but I don't think there's a way to retrieve it, so in order to send them one, we probably could simply call ApproveMembership like you suggest.  ApproveMembership will delete the user, and re-create it but it's already hooked up for sending the email.  Seems a lot simpler.  I guess that's why we have discussion lists, right?  :^)

Mike

Aug 28, 2008 at 8:36 PM
Well, I tried...

I started from the function given here : http://www.codeplex.com/CKS/WorkItem/View.aspx?WorkItemId=6470 and tried this :

        protected override void OnCreatingUser(LoginCancelEventArgs e)
        {
            SPSite site = SPControl.GetContextSite(Context);
           
            /* 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);

                //only do this if review feature is null (auto-approval)
                if (site.Features[new Guid("{69CE2076-9A2F-4c71-AEDF-F4252C01DE4E}")] == null)
                {
                    base.OnCreatingUser(e);
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                            MembershipRequest.ApproveMembership(request, site.RootWeb);
                    });
                }
            }
            this.MoveTo(this.CompleteStep);
        }

But it doesn't work... As I don't know what is the purpose of base.OnCreatingUser(e); or delegate(), I probably don't use them as I should... I just copied them because they were here in the previous function.  ;o)

If you could give me some advice... or if you found a better way to do this... just tell me.  :o)

Thanks !
Developer
Aug 28, 2008 at 10:55 PM
The base.OnCreatingUser(e) calls the ASP.NET base class that actually does the creation.  So, when the call to ApproveMembership happens, the user will already exist in the database with an autogenerated password.  ASP.NET knows about our membership provider, and it handles the actual database update.  That's why the ApproveMembership method deletes any user it finds with that username, and re-adds it with a newly generated password.  Actually, doing it this way you might not even need to call the base class, since the ApproveMembership calls CreateUser itself. It was in the original "else" block because otherwise the user would never get added.

But I think what the trouble is that your SPSite is created outside of the elevated privileges block.  Even though you're executing the method with elevated privileges, the SPSite was created under the annonymous account.  That's why the original code had a SPSite2 created in that block.  Now you're executing the whole ApproveMembership function, and while that should be working with elevated privileges, the object itself that's being passed isn't.

So, moving

    using (SPSite site2 = new SPSite(this.Page.Request.Url.ToString()))
    {
        using (SPWeb web = site2.RootWeb)
        {

into the delegated privileges should work.  I'm wrapping it in a using block, because SPSite and SPWeb are small bits of managed code with very large amounts of unmanaged code in them, which consumes a lot of resources.  The garbage collector sees the small managed part, and isn't real dilligent about reclaiming those resources.  So you force the issue by using the "using" block, or else calling dispose() when you're done with them.

Give that a shot and see if it works. 

Mike
Developer
Aug 28, 2008 at 10:57 PM

Oh, wait a minute...the request is going to have the same trouble.  You'll probably need to put the whole thing into elevated privileges.

 

Mike

Aug 29, 2008 at 5:13 PM
It doesn't work...
I tried a few changes, I don't know where is the problem, nothing works.  :o(
User is added but not approved.

I reached my limits here...  :o/
Developer
Aug 29, 2008 at 5:46 PM
Ok, I'll take a look.  Actually, I was contacted by a fellow this morning from the UK who might have this fixed, or nearly so.  But he's in the UK, so it's probably past his normal workday today.  I'll keep you posted.

Regards,
Mike Sharp
Aug 29, 2008 at 5:51 PM
Thanks !

I read again my precedent post and I think it was a little negative. But be sure I appreciate your help until now, I just feel a little disappointed to not manage to correct this myself. I have to learn this kind of dev...

(And sorry for my english, this isn't my mothertongue...  ;oP )
Developer
Aug 29, 2008 at 5:56 PM
No problem!

Actually, your english is great. Until you mentioned it, I hadn't realized you weren't a native speaker!  In fact, I was thinking you were in the same time zone as me, since we seem to be working at the same time

Mike
Aug 29, 2008 at 6:23 PM
Thanks, I always hesitate on verb tenses for instance, but that's good news for me because I'm always afraid to be misunderstood !  ;o)
Actually I'm in the same time zone (or about), but living in Québec (only for 2 years, coming from France).

To come again on the code, that's how look my code for the moment :

        protected override void OnCreatingUser(LoginCancelEventArgs e)
        {
            Guid webGuid = SPContext.Current.Web.ID;
            Guid siteGuid = SPContext.Current.Site.ID;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(siteGuid))
                {
                    using (SPWeb web = site.OpenWeb(webGuid))
                    {
                        /* 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);
                            //only do this if review feature is null (auto-approval)
                            if (site.Features[new Guid("{69CE2076-9A2F-4c71-AEDF-F4252C01DE4E}")] == null)
                            {
                                MembershipRequest.ApproveMembership(request, web);
                            }
                        }
                        this.MoveTo(this.CompleteStep);
                    }
                }
            });
        }

I tried different things around impersonation, like your proposal or like in my last code, but the result is the same. I tried to set request.UserName = "xxxx"; and it seems that the program doesn't run until there because the user name in users list is still the same than registered by the user, and not xxxx.
Or maybe I shouldn't use "this." in the SPSecurity.RunWithElevatedPrivileges part ? That's that kind of things I don't know...


Aug 29, 2008 at 6:38 PM
just to add my "2 cents" to this thread; My implementation is using a custom login page in the _layouts directory that has anonymous access permissions. I tried to hack an auto approve solution in the src code upon user authentication from there, but always ended up with permission denied errors upon the attempt to auto add of the new user to a sharepoint group; tried elevated permissions and various impersonation approaches; no luck at all. So I ended up selling the manual approval via the membership request  to my stakeholders instead. (" I just love to negotiate" Capt Kirk)