Custom MembershipService

Feb 7, 2011 at 10:53 AM

Hi,

I would like to implement custom authentication against a existing DB. I've had a breif look at how the OpenAuth module is implemented, but I think thats more work that what is needed for my use case.

From what I can see I just need to implement the IMembershipService or possibly inherit the the existing MembershipService and override the GetUser and ValidateUser methods.

Question: is it possible to have the dependency injector use MyMembershipService instead of the exisitng MembershipService? what is the best way to implement this?

Developer
Feb 7, 2011 at 12:33 PM

Hi!

This depends on what you really want to achieve. If you'd like only to check username/password then yes (no custom authentication scheme - single-sign-on and such) - implementing custom IMembershipService is the only thing you might want.

You can inherit the default MembershipService and override only the methods you mentioned (GetUser and ValidateUser), but that would mean that you don't want to create new users (CreateUser method) in your existing DB and don't want users to change their passwords (ChangePassword method)... If you want both of those functionalities too, you should just implement the IMembershipInterface in whole.

About DI - yes, by default, objects created inside custom modules have higher priority over the core ones and will become the default implementations injected by IoC container. So there's nothing more for you to do. Of course the old object is still accessible if you need that - you can get it's instance by injecting IEnumerable<IMembershipService> - this will get you all existing interface implementations (old and new ones).

Cheers, Piotr

Nov 18, 2012 at 3:11 PM

Well, I have created my custom  MembershipService module.

But it works does'n work well.

When I entering wrong login/passwd Orchard indicates that:

Login was unsuccessful. Please correct the errors and try again. 
The username or e-mail or password provided is incorrect.

But when I enter correct - nothing is happend: no dasboard, no logout menu.

How does I have to maintain this? How does roles provider work? Should I reimplement it? May I relay on Orchard mechanism?

 

My module structure:

Free Image Hosting at www.ImageShack.us

 and in InspireMembershipService.cs file I just created new class:

 

 

namespace InspireMembershipService.Services
{
    [OrchardSuppressDependency("Orchard.Users.Services.MembershipService")]
    public class InspireMembershipService : IMembershipService
    {
        private readonly IOrchardServices _orchardServices;
        private readonly IMessageManager _messageManager;
        private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
        private readonly IEncryptionService _encryptionService;

        private string connectionString = "server=localhost; user id=11111; password='11111'; database=11111; pooling=false;";
        private string tableNameUsers = "UserTable";

        public InspireMembershipService(IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers, IClock clock, IEncryptionService encryptionService)
        {
            _orchardServices = orchardServices;
            _messageManager = messageManager;
            _userEventHandlers = userEventHandlers;
            _encryptionService = encryptionService;
        }

 

And implemented all functions from IMembershipService. For example:

 

      public IUser ValidateUser(string userNameOrEmail, string password)
        {
            using (new TransactionScope(TransactionScopeOption.Suppress))
            {
                var user = _orchardServices.ContentManager.New<UserPart>("User");

                user.Record.RegistrationStatus = UserStatus.Approved;
                user.Record.EmailStatus = UserStatus.Approved;

                string username = userNameOrEmail;
                bool isValid = false;

                MySqlConnection conn = new MySqlConnection(connectionString);
                MySqlCommand cmd = new MySqlCommand("SELECT Passwd, IsApproved FROM `" + tableNameUsers + "`" +
                        " WHERE Login = ?Username AND IsLockedOut = 0", conn);

                cmd.Parameters.Add("?Username", MySqlDbType.VarChar, 255).Value = username;

                MySqlDataReader reader = null;
                bool isApproved = false;
                string pwd = "";

                try
                {
                    conn.Open();

                    using (reader = cmd.ExecuteReader(CommandBehavior.SingleRow))
                    {
                        if (reader.HasRows)
                        {
                            reader.Read();
                            pwd = reader.GetString(0);
                            isApproved = reader.GetBoolean(1);
                            SetPassword(user.Record, pwd);
                            user.Record.UserName = username;
                            user.Record.NormalizedUserName = user.Record.UserName.ToLowerInvariant();
                            user.Record.HashAlgorithm = "SHA1";

                        }
                        else
                        {
                            return null;
                        }
                        reader.Close();
                    }

                    if (ValidatePassword(user.As<UserPart>().Record, password))
                    {
                        if (isApproved)
                        {
                            isValid = true;
                            MySqlCommand updateCmd = new MySqlCommand("UPDATE `" + tableNameUsers + "` SET LastLoginDate = ?LastLoginDate, LastActivityDate = ?LastActivityDate" +
                                                                    " WHERE Login = ?Username", conn);

                            updateCmd.Parameters.Add("?LastLoginDate", MySqlDbType.DateTime).Value = DateTime.Now;
                            updateCmd.Parameters.Add("?LastActivityDate", MySqlDbType.DateTime).Value = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
                            updateCmd.Parameters.Add("?Username", MySqlDbType.VarChar, 255).Value = username;

                            updateCmd.ExecuteNonQuery();
                        }
                    }
                    else
                    {
                        conn.Close();
                    }
                }
                catch (MySqlException e)
                {
                    throw e;
                }
                finally
                {
                    if (reader != null) { reader.Close(); }
                    conn.Close();
                }

                if (isValid)
                {
                    return user;
                }
                else
                {
                    return null;
                }
            }
        }

 

 

 

Nov 18, 2012 at 11:37 PM

You create a new user for every login.... this is incorrect.

You can implement the Interface as you have done but it might be easier for you to inherit the existing membership service as there are a few methods that you will need. GetUser and CreateUser

What you want to do is validate the username and password, if its all good call GetUser to see if the user has logged in before and been created if so return the user. If the user is null create a new user with the CreateUser method.

Nov 19, 2012 at 8:26 AM
Thank you jrmurdoch for reply. As I understand my mistake was in this part: 

var user = _orchardServices.ContentManager.New<UserPart>("User");

So how should I call GetUser after validating username and pass? I can't understand, please explain or give me some hint with code example.
Thanks in advance.


Nov 19, 2012 at 8:54 AM

I'll add some code snippets in the hope you can fill in the blanks:

Define you class as:

using IMembershipService = Orchard.Security.IMembershipService;
using OrchardUsers = Orchard.Users.Services;

namespace MyModule.Orchard.Users.Services {
    public class MembershipService : OrchardUsers.MembershipService, IMembershipService

Then in the method, notice the NEW in the declaration

public new IUser ValidateUser(string userNameOrEmail, string password)

Add you code to validate against your DB i.e.

If user name and password is correct

// Try and get Orchard User. If no user exists in Orchard, then create them one here.
            var user = GetUser(userNameOrEmail) ?? CreateUser(new CreateUserParams(userNameOrEmail,
                                                        userNameOrEmail, userNameOrEmail),
                                                        null,
                                                        null,
                                                        null,
                                                        true));

This last line might not be needed but it wont hurt:

// Reload user to refresh
            return GetUser(user.UserName);
If you is not authenticated return null, but i'm sure that part is working correctly for you.

Thats it.

 

 

 

Nov 19, 2012 at 8:33 PM

Thank you jrmurdoch! Your magic are working! Awesome!

In future maybe someone will be interested in this code:

using IMembershipService = Orchard.Security.IMembershipService;
using OrchardUsers = Orchard.Users.Services;

namespace MyModule.Orchard.Users.Services {
    public class MembershipService : OrchardUsers.MembershipService, IMembershipService
    {
        private readonly IOrchardServices _orchardServices;
        private readonly IMessageManager _messageManager;
        private readonly IEnumerable<IUserEventHandler> _userEventHandlers;
        private readonly IEncryptionService _encryptionService;

        private string connectionString = "server=1111; user id=111; password='111'; database=111; pooling=false;";
        private string tableNameUsers = "1111";

        public MembershipService (IOrchardServices orchardServices, IMessageManager messageManager, IEnumerable<IUserEventHandler> userEventHandlers, IClock clock, IEncryptionService encryptionService) 
            : base ( orchardServices,  messageManager, userEventHandlers,  clock,  encryptionService)
        {

        }

public IUser CreateUser(CreateUserParams createUserParams)
        {
// my custom DB modification for user table
}

   public IUser ValidateUser(string userNameOrEmail, string password)
        {
            string username = userNameOrEmail;
            bool isValid = false;
            bool isApproved = false;
            string pwd = "";

//necessary for MySQL transactions
            using (new TransactionScope(TransactionScopeOption.Suppress))
            {

              // database action            

            } 
                if (isValid)
                {
                    var user = GetUser(userNameOrEmail) ?? base.CreateUser(new CreateUserParams(userNameOrEmail, pwd, userNameOrEmail,
                                                        null,
                                                        null,
                                                        true));

                    return user;
                }
                else
                {
                    return null;
                }
        }

Jan 29, 2013 at 9:19 PM

How do you implement this, as a module? If so can you provide us a copy of it working?