I'm trying to create a custom membership provider to authenticate users in Umbraco to an already existing database outside Umbraco. From what I've learned so far it should not be much more work than create a class that inherits from umbraco.providers.members.UmbracoMembershipProvider and overrides the ValidateUser() function.
What I'm wondering is how I'm supposed to do when I wish to include this class to my Umbraco project other than specify it in the web.config file? Is it possible to extend Umbraco in this way whitout re-compile the source code?
EDIT - I wrote an article about how I implemented this on http://marcus-abrahamsson.se/post/Membership-Provider-in-Umbraco
You can create and build your own module and then copy the .dll into the bin folder of the Umbraco install. No need to recompile Umbraco itself.
I had a very similar issue, but perhaps with a smaller requirement. I was trying to implement the "last logged in" functionality as described in an Umbraco TV video, to display the last logged in date of members. As the code base has changed, the described method was no longer valid. I found there were 2 general ways to get it to work:
Create your own membership provider. Inherit from UmbracoMembershipProvider and update web config to use your provider. I overrode one method, and my provider code looked like:
using umbraco.BusinessLogic;
using System.Web.Profile;
using System;
namespace zo.Umb.LastLogin
{
// this approac works, and it may be necessary to extend the membership provider in the future, so that's why I'm
// leaving it here. But for now I'm using the ApplicationStartupHandler event subscription method
// in MemberEvent.cs
/// <summary>
/// Inherit the default membership provider and substitute my own method that's fired when a member tries
/// to log in. Note that you must also replace the UmbracoMembershipProvider reference in the web.config
/// with a reference to this one. eg:
/// <add name="UmbracoMembershipProvider" type="zo.Umb.LastLogin.MyMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Another Type" passwordFormat="Hashed" />
///
/// also note that, to have custom profile properties appear, they must also be added in the web.config
/// like so:
/// <profile defaultProvider="UmbracoMemberProfileProvider" enabled="true">
/// <providers>
/// <clear />
/// <add name="UmbracoMemberProfileProvider" type="umbraco.providers.members.UmbracoProfileProvider, umbraco.providers" />
/// </providers>
/// <properties>
/// <clear />
/// <add name="lastLogin" allowAnonymous ="false" provider="UmbracoMemberProfileProvider" type="System.DateTime" />
/// </properties>
///</profile>
/// </summary>
public class MyMembershipProvider : umbraco.providers.members.UmbracoMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
var success = base.ValidateUser(username, password);
if (success)
{
var user = GetUser(username, true);
var profile = ProfileBase.Create(user.UserName);
profile["lastLogin"] = DateTime.Now;
profile.Save();
}
return success;
}
}
}
ValidateUser is fired when the user tries to get to secured content.
Note that you now have to update the web.config to point to your custom membership provider.
While this worked, I didn't like moving off of the default provider, editing web.config, etc. and wanted to use the more "standard" approach of the previous version. So I used method 2:
Wire into ApplicationStartupHandler, and subscribe to Member.BeforeSave
using System;
//using umbraco.BusinessLogic;
using umbraco.businesslogic;
using umbraco.cms.businesslogic.member;
namespace zo.Umb.LastLogin
{
public class MemberEvent : ApplicationStartupHandler
{
public MemberEvent()
{
Member.BeforeSave += new Member.SaveEventHandler(Member_BeforeSave);
}
void Member_BeforeSave(Member sender, umbraco.cms.businesslogic.SaveEventArgs e)
{
//Log.Add(LogTypes.Debug, sender.Id, "Member_AfterAddToCache");
sender.getProperty("lastLogin").Value = DateTime.Now;
}
}
}
If I recall, member.beforesave is not something that's fired when a user is created and saved to the membership store; it's actually fired when a user logs in.
Related
Iam new to asp.net membership & I need help to change its connection string programmatically.
What I have tried till now is
I have create a class project name Sample as namespace** and extends the System.Web.Security.SqlMembershipProvider
code as
namespace Sample
{
public class Connectionstring : SqlMembershipProvider
{
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string connectionString = "server=xx.xx.xx;database=db;user id=un;password=pwd";
// Set private property of Membership provider.
FieldInfo connectionStringField = GetType().BaseType
.GetField("_sqlConnectionString", BindingFlags.Instance |
BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
}
}
and altered web config file in membership tag as
<membership defaultProvider="SQLMembershipProvider">
<providers>
<add name="SQLMembershipProvider" type="sample.Connectionstring,sample" connectionStringName="SQLMembershipConnString" applicationName="#######" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" />
</providers>
</membership>
and while running the web application project the connection string which i am changing is not get altered?
waiting for your valuable responses and comments
What i have also found on the net about problem is this here:
A simpler, albeit somewhat eyebrow-raising solution is just modifying
the connection string in the providers early enough in the request's
lifecycle:
private void SetProviderConnectionString(string connectionString)
{
var connectionStringField =
Membership.Provider.GetType().GetField("_sqlConnectionString",
BindingFlags.Instance | BindingFlags.NonPublic);
if (connectionStringField != null)
connectionStringField.SetValue(Membership.Provider, connectionString);
}
Calling this method from Global.asax.cs inside
Application_PreRequestHandlerExecute does the job. Haven't tested it
too much, but even if something doesn't work, it just means it needs
to be done earlier. No guarantees this will work with future versions
of the framework, although most likely it will.
So, the 'SetProviderConnectionString' method can be called manually (after the Initialize method is finished), instead of expecting the framework to call the overwritten Initialize method when the Membership.Provider is referenced for a first time.
You don't appear to be calling base.Initialize from your Initialize override. I'm surprised anything works in this case.
In .NET 4 (not sure about earlier versions), you should be able to add the connection string to the configuration collection before calling base.Initialize as follows:
public override void Initialize(string name, NameValueCollection config)
{
config["connectionString"] = ... whatever ...;
base.Initialize(name, config);
}
This avoid the need to use reflection to access a private field.
For what reason are you not using a connection string in the web config?
Typically the web server will access the database using the web server's credentials, not an individual user's credentials.
This is a generic setup guide for asp.net authentication.
http://vstudiojourney.blogspot.com/2008/06/choosing-another-database-for.html
Normally use authorization settings to interact with the roles.
<location path="Register.aspx">
<system.web>
<authorization>
<allow roles="Administrator"/>
<deny users="?"/>
</authorization>
</system.web>
</location>
But I would like to have this setup in the database in a table
tblAuthorization
-IdAuthorization (Identy(1,1))
-IdCompany=5
-IdRol=5 (Administrator”)
-Path=”Register.aspx”
Is there a setting for a class to do this? There is something like Profile, RoleProvider ..
<roleManager enabled="true" defaultProvider="MyAccessRolProvider">
<providers>
<clear />
<add name="MyAccessRolProvider" type="MyAccessRolProvider" connectionStringName="MyConnectionString" applicationName="/" />
<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
</providers>
</roleManager>
Right now the only I think that can to implement a validation in the Page_Load event
And if it is invalid making a Redirect but I would do a little more "Professional"
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if(!ValidateAuthorization())
{
Response.Redirect("Login.aspx");
}
}
}
Could help me with examples?
Thank you very much in advance
Your best bet is to implement a custom RoleProvider - you seem to be heading in this direction as your question includes a configuration section with a custom RoleProvider (MyAccessRolProvider).
You need to specify the fully-qualified name of your RoleProvider, e.g. MyNamespace.MyAccessRoleProvider, MyAssembly.
MSDN describes how to do this, including a sample implementation.
With the understanding that you are taking about authorization at the page level you could add a HTTP module (IHttpModule) and use your module to do the authorization checking. If the authorization failed then you could redirect as appropriate.
Something like this might work:
public class MyAuthorizationModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthorizeRequest += (new EventHandler(AuthorizeRequest));
}
private void AuthorizeRequest(Object source, EventArgs e)
{
bool isAuthorized = //your code here to check page authorization
if (!isAuthorized)
{
var response = //will need to get current response
response.Redirect("Login.aspx", true);
}
}
}
Note: Don't forget you'll need to add the module to your web application.
There is no out of the box support for this as far as I know however I would suggest you implement something as follows:
Create a new class that derives from AuthorizeAttribute, add an ID property and override the "AuthorizeCore" method.
public class CustomAutorizeAttribute : AuthorizeAttribute
{
public Guid ActionId { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Check user has access to action with ID = ActionId
//implementation omitted
}
}
You can add a new database table to hold on the Actions and their associated ID values. You can either add the record to the table manually or write some code to do this using reflection (I would advise this if you have quite a few actions and are likely to add more in the future).
Once this is done you can assign users or application roles access to these actions.
In you custom Authorize attribute you have to check whether the user has access to the given action returning true or false accordingly from the AuthorizeCore method.
I would advise grabbing a collection of all actions a user has access to when a user logs in and storing them in either the server cache or some kind of distributed cache so that you aren't hitting your database on every request.
UPDATE - Sorry just realised you're using ASP.NET Forms and not MVC. My approach is for an MVC application so its probably not relevant although I'll leave it up here in case any MVC developers happen to stumble across your question.
I am using code that inherits SqlMembershipProvider, named MyMembershipClass so that I can make use of the functions.
I have created my inherited class and specified it in the Web.config file like so:
<membership defaultProvider="MyMembershipClass">
<providers>
<clear/>
<add name="MyMembershipClass" type="MyMembershipClass" applicationName="/"/>
</providers>
</membership>
Now that I have done that, to make use of the Membership functions, do I refer to it within my code as:
MyMembershipClass or just Membership?
EDIT:
If you refer to it at just Membership, is this due to defaultProvider="MyMembershipClass"?
Just as Membership. It will automatically map to your defined membership class. That enables to change the provider later without needing to change all of your code.
So you don't have to write:
MyMembershipClass.GetUser
but
Membership.GetUser
By the way, the same is true if you define a custom RoleProvider, for example:
Roles.AddUserToRole(userName, roleName);
Edit:
If you refer to it at just Membership, is this due to
defaultProvider="MyMembershipClass"?
Yes, that must be the name of your class that inherits from MembershipProvider (or another class inheriting from it like SqlMembershipProvider).
According to your second comment that you can define what you want even if it doesn't exist, i get an exeption if i name the provider "Bob" in web.config:
Exception information:
Exception type: ConfigurationErrorsException
Exception message: Default Membership Provider could not be found.
Just Membership. The static methods on Membership will call the methods on your provider.
I've run into a bit of a pickle here. I have a custom profile that I used in two applications in the same solution. The first was a web application that I built to import a custom set of user profiles and attributes from an old .net application into the .net membership membership, roles, profile tables. I built a profile common class that inherits from profilebase. Both applications have a copy of the same class within their namespaces.
using System;
using System.Web.Security;
using System.Web.Profile;
using System.Collections.Specialized;
namespace WebProject
{
public class ProfileCommon : ProfileBase
{
public static ProfileCommon GetUserProfile(string username)
{
return Create(username) as ProfileCommon;
}
public static ProfileCommon GetUserProfile()
{
return Create(Membership.GetUser().UserName) as ProfileCommon;
}
[SettingsAllowAnonymous(false)]
public string FirstName
{
get
{
return base["FirstName"] as string;
}
set
{
base["FirstName"] = value;
}
}
[SettingsAllowAnonymous(false)]
public string LastName
{
get
{
return base["LastName"] as string;
}
set
{
base["LastName"] = value;
}
}
[SettingsAllowAnonymous(false)]
public string Email
{
get
{
return base["Email"] as string;
}
set
{
base["Email"] = value;
}
}
[SettingsAllowAnonymous(false)]
public StringCollection Sites
{
get
{
return base["Sites"] as StringCollection;
}
set
{
base["Sites"] = value;
}
}
}
}
My profile provider section in my web config file looks like this.
<profile defaultProvider="WebProjectProfileProvider" inherits="WebProject.ProfileCommon">
<providers>
<clear />
<add name="WebProjectProfileProvider" applicationName="/" type="System.Web.Profile.SqlProfileProvider" connectionStringName="Test"/>
</providers>
</profile>
If I use one application to perform the user import and another that uses the membership, roles and profiles that I created would this cause the "The settings property '' was not found." error? I can't seem to pinpoint where the error is being caused and some of the most common causes I've already checked. This is the first time I've used this feature in .net on such a large scale. Any help is greatly appreciated.
Thanks.
I found my problem. The issue was in the calling code. I had run into so many issues regarding the profile that I forgot to change the calling code back to the static method
ProfileCommon.GetUserProfile();
The other issues I had run into as well was declaring the profile's properties in the web config and declaring them in a profile common class. This was causing me to get flip flopping errors such as "the property has already been defined." and "The settings property '' was not found."
In short, declare the ProfileCommon proxy class within code and not in the web.config IF you are using a "Web Application" solution. Declare the properties in web.config if you are using a "web site" solution.
The best example I've come accross on the web was from this site.
ASP.NET Profiles in Web Application Projects
It describes how to use custom profiles in a nice concise summary and gives a full explanation of why this method is performed for web applications and why it's done differently for web sites. Hope this saves many head aches.
I have multiple calls to Membership.GetUser() and can't seem to find anything built in to cope with this (without hitting the database multiple times).
I first looked into static variables before realising this was not suitable. I know I can use sessions but I would need to hash/encrypt the userID to make it safe, which is fine but I didn't want to do this without checking if there was a built in way to do this within the .net membership provider
Is there a built in way of remembering a value like the UserID and safely storing that for the current user?
I recommend using the Singleton-per-Request pattern which uses the HttpContext.Items property.
public class UserPerRequest
{
/// <summary>
/// Returns the result of Membership.GetUser(), but will cache the results within the
/// current request so it's only called once per request.
/// </summary>
public static MembershipUser Current
{
get
{
const string key = "UserPerRequest";
if (HttpContext.Current.Items[key] == null)
HttpContext.Current.Items[key] = Membership.GetUser();
return (MembershipUser)HttpContext.Current.Items[key];
}
}
}
So everywhere you'd call Membership.GetUser() just call UserPerRequest.Current instead. The only time you'll still get multiple calls to the database during a request is if Membership.GetUser() returns null.
Membership.GetUser() is an indirect call to the class MembershipProvider's method:
public abstract MembershipUser GetUser(string username, bool userIsOnline);
So, you can write a class that derives from the MembershipProvider you're using (for example SqlMembershipProvider or ActiveDirectoryMembershipProvider, or a custom one) and override this method using some clever cache mechanism.
Then you will need to change the web.config to declare this new MembershipProvider class:
<membership defaultProvider="MyProvider">
<providers>
<clear/>
<add name="MyProvider" type="...MyProvider" ... />
</providers>
</membership>