ASP.NET Membership for one website but multiple and potentially unknown sql server instance - asp.net-membership

This my problem. I have 1 and only 1 website for multiple customer. To access to their data, customers use urls like this :
http://customer1.mysite.com
http://customer2.mysite.com
etc
Each customer have a SqlServer instance with their data.
Depends to the URL the website connect to the right sql server instance. This is good.
My issue is about Membership, each instance have is own "membership database". In my webconfig I configure a dummy section like this :
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider"
type="MapApp.MyMembershipProvider, MyApp"
enablePasswordRetrieval="true"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Encrypted"
minRequiredPasswordLength="5"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
connectionStringName="A_DATABASE" />
</providers>
</membership>
Also I have a custom Membershipprovider with code like this :
public class MyMembershipProvider : SqlMembershipProvider
{
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
// Update the private connection string field in the base class.
string connectionString = "my new connection string depdend of the customer"
// Set private property of Membership provider.
FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
}
but is not enough a have some issues like "already instanciate" when I call my custom membership provider.
Someone can help me ? Please...
Sorry for my bad english in this long post

This is an interesting problem. Seems like you are attempting to use a different connection string for the database, based on the URL. This might be a bit less elegant, but how about getting the code for the sql membership provider and modifying it so that the connection string which it uses is based on the users URL. I think this could be a good work around if you are truly only using one asp.net application.

I think I have the solution but it's a bit strange.
In my customMemberShipProvider class I must have an empty constructor even if I never call it. And I need another constructor (with args even if I never use it).
So this is my code :
public class myMembershipProvider : SqlMembershipProvider
{
public myMembershipProvider()
{
//never use
}
public myMembershipProvider(string dummyArg)
{
string configPath = "~/web.config";
Configuration config = WebConfigurationManager.OpenWebConfiguration(configPath);
MembershipSection section = (MembershipSection)config.GetSection("system.web/membership");
ProviderSettingsCollection settings = section.Providers;
NameValueCollection membershipParams = settings[section.DefaultProvider].Parameters;
if ((HttpContext.Current.Session != null) && (HttpContext.Current.Session["SubDomainInstanceName"] != null) && (!string.IsNullOrEmpty(HttpContext.Current.Session["SubDomainInstanceName"].ToString())))
{
membershipParams.Add("Instance", HttpContext.Current.Session["SubDomainInstanceName"].ToString());
}
else
{
membershipParams.Add("Instance", "default");
}
Initialize(section.DefaultProvider, membershipParams);
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
//need to remove the key before call Initialize method
string instance = config["Instance"];
config.Remove("Instance");
base.Initialize(name, config);
// Update the private connection string field in the base class.
string connectionString = //my specific connectionstring;
// Set private property of Membership provider.
FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
Regards

Related

Oracle EF 6 Code first: How to secure the password field in the connection string in app.config

I am developing an Windows application with Oracle EF 6 Code first. It works fine, however, the password is stored as a plain text in the connection string in app.config:
<connectionStrings>
<add name="OracleDbContext" providerName="Oracle.ManagedDataAccess.Client" connectionString="User Id=UsrName;Password=password;Data Source=ds" />
</connectionStrings>
What is the best practice to secure the password? I tried to load the connection string and provide the password at run time. Then pass the whole connection string to DBContext via constructor:
public class OracleDbContext : DbContext
{
public OracleDbContext(string connectionstring)
:base(connectionstring)
// : base("name=OracleDbContext")
{
}
...
}
}
public OracleDbContext CreateDbContext()
{
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["OracleDbContext"];
string connectString = settings.ConnectionString;
OracleConnectionStringBuilder builder = new OracleConnectionStringBuilder(connectString);
if (String.IsNullOrEmpty(builder.Password))
{
builder.Password = "password";
}
return new OracleDbContext(connectString);
}
but, It failed with an error "Unable to complete operation. The supplied SqlConnection does not specify an initial catalog or AttachDBFileName."
In this MSDN article you will find some information about encrypting connection strings inside web.config / app.config.
I use this way to separate the connectionstring for user.config, but this can easy be tweaked for inserting user/pwd in runtime.
<appSettings file="user.config">
<add key="db" value="DATA SOURCE=server:1521/instance;PASSWORD=password;PERSIST SECURITY INFO=True;USER ID=user"/>
public static string CreateEFConnectionString()
{
var connectionString = ConfigurationManager.AppSettings["db"];
var builder =
new EntityConnectionStringBuilder(
"metadata=res://*/EF_Model.csdl|res://*/EF_Model.ssdl|res://*/EF_Model.msl;provider=Oracle.ManagedDataAccess.Client;")
{
ProviderConnectionString = connectionString
};
return builder.ConnectionString;
}
using (var en = new Entities(Dbutils.CreateEFConnectionString()))
{}
.
public Entities() : base("name=Entities")
{
}
public Entities(string connectionstring) : base(connectionstring)
{
this.Configuration.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
}

Global API security checks with ASP.Net MVC3

We are planning on using ASP.Net MVC3 to create a JSON API. For handling security we will have something like an API key, or possibly the Username/Password and maybe a timestamp.
I haven't done any MVC before but I'm wondering if there isn't some simple way to add code to Global.asax that ensures that ALL requests have these variables in them somehow. That way, no request could even get through unless it included the API key.
That way we don't have to add API key handling to each section of the site.
Create a global authorization filter -
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class MyAuthorizationFilterAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// do Authorization
}
}
then register it in Global.asax -
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyAuthorizationFilterAttribute());
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Although you could create a bespoke Authentication module -
public class CustomAuthentication : IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(this.Authenticate);
}
public void Authenticate(object source, EventArgs eventArgs)
{
HttpApplication _application = (HttpApplication)source;
HttpContext _context = _application.Context;
// do authentication
// if authenticated set pricipal
// _context.User = new GenericPrincipal(new GenericIdentity("user"), new string[]);
}
public void Dispose() { }
}
Then you just need to register the module in web.config
<modules runAllManagedModulesForAllRequests="true">
<add name="CustomAuthentication" type="AuthenticationNamespace.CustomAuthentication"/>
</modules>
and set asp.net authentication to none -
<authentication mode="None">
</authentication>
Then you can check to see it the user is authenticated in your AuthorizationFilter.
if(HttpContext.Current.Request.User.Identity.IsAuthenticated)

Multi Tenant SQLMembershipProvider ASP.NET MVC

I am trying to move to Azure (including SQL Azure) with a multi-tenanted ASP.NET MVC application. Each customer gets their own database which is self-contained, including all of their membership credentials.
We are able to set the connection string to the SqlMembershipProvider on Initialization of the SqlMembershipProvider object. However subsequent requests to different sub domains (in the same session) do not change the connection string. I have found an example where the implementation overrides the SqlMembershipProviders ConnectionString but this is not possible in version 4.0 of the System.Web dll.
We could implement a single membership database and authenticate against that......but we would like to keep the customers credentials isolated in this SAAS model.
So the question is how do I change the SQLMembershipProviders connection string dynamically for each request?
Web.config
<membership defaultProvider="TenantMembershipProvider">
<providers>
<clear/>
<add name="TenantMembershipProvider" type="ABC.Infrastructure.MultiTenancy.TenantMembershipProvider, ABC"
connectionStringName="ApplicationServices" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false"
requiresUniqueEmail="false" passwordFormat="Clear" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" applicationName="/"/>
</providers>
</membership>
TenantMembershipProvider.cs that handles the initialization
public class TenantMembershipProvider : SqlMembershipProvider
{
private SiteLinqSession _session;
private MasterSession _masterSession;
private static readonly Dictionary<string, Customer> _customers = new Dictionary<string, Customer>();
private static string _host;
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
string connectionString = GetConnectionString();
FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
private string GetConnectionString()
{
var headers = HttpContext.Current.Request.Headers["Host"];
string[] host = headers.Split('.');
_host = host[0];
if (_host == "127") _host = "demo";
var customer = GetSite(_host);
return BuildTenantConnectionString(customer.ConnectionSetting);
}
private Customer GetSite(string host)
{
Customer customer;
//check dictionary if customer exists for the subdomain
_customers.TryGetValue(host, out customer);
if (customer != null)
return customer;
//if not get the customer record and add it to the dictionary
_masterSession = new MasterSession();
var customers = _masterSession.All<Customer>();
customer = customers.SingleOrDefault(x => x.SubDomain == _host);
if (customer != null)
_customers.Add(host, customer);
return customer;
}
private string BuildTenantConnectionString(ConnectionSetting setting)
{
return string.Format("Data Source={0};Initial Catalog={1};User Id={2};Password={3};", setting.DataSource, setting.Catalog, setting.Username, setting.Password);
}
}
To save people some time from the link that Adam posted.
In your Global.asax file for the Application_PreRequestHandlerExecute event
protected void Application_PreRequestHandlerExecute()
{
SetProviderConnectionString(GetConnectionString());
}
private void SetProviderConnectionString(string connectionString)
{
// Set private property of Membership, Role and Profile providers. Do not try this at home!!
var connectionStringField = Membership.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
if (connectionStringField != null)
connectionStringField.SetValue(Membership.Provider, connectionString);
var roleField = Roles.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
if (roleField != null)
roleField.SetValue(Roles.Provider, connectionString);
var profileField = ProfileManager.Provider.GetType().GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
if (profileField != null)
profileField.SetValue(ProfileManager.Provider, connectionString);
}
private string GetConnectionString()
{
return string.Format("Data Source={0};", #".\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|demo.mdf;User Instance=true");
}
if you created a custom membershipProvider then you would get the BaseType instead
var connectionStringField = Membership.Provider.GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
I'm not sure if this is the most appropriate solution, but it seems to get the job done to get dynamic connectionString enabled for the membershipProvider without rolling your own. Feels a little bit hackish though.
You need to get this early in the request cycle in Application_PreRequestHandlerExecute
Note the code from http://forums.asp.net/p/997608/2209437.aspx
There are several ways,one is to check out the sample provider code and use (I dont think this is what you referred to above?)
SqlConnectionHelper (fron the link above)
internal static string GetConnectionString(string specifiedConnectionString, bool lookupConnectionString, bool appLevel)
{
//Your Conn String goes here!!
return Factory.ConnectionString;
}
the other involves the prerequestauthenticate. You could potentially store the string in the session (set it upon initial login) and reference it in the provider or use the reflection based code if all else fails although it doesn't seem clean. If those don't work then you'll need to roll your own provider.

Change ProfileProvider connectionstring on the fly

If you read my previous post, you know I'm able to use a custom MembershipProvider and RoleProvider to use different datasource on each call on the fly. I want to to the same thing with Profile.
My profile's propteries are not store in the web config but in a custom class like this :
public class AccountProfile : ProfileBase
{
public override SettingsProviderCollection Providers
{
get
{
return base.Providers;
}
}
static public AccountProfile GetUserProfile(string userName)
{
return (AccountProfile)(ProfileBase.Create(userName));
}
[SettingsAllowAnonymous(false)]
public string MobilePhone
{
get { return ((string)(base["MobilePhone"])); }
set { base["MobilePhone"] = value; Save(); }
}
}
also like for the Membership and RoleProvider I have a class like this :
public class MyProfileProvider : SqlProfileProvider
{
public MyProfileProvider()
{
}
public MyProfileProvider(string SubDomainInstanceName)
{
string configPath = "~/web.config";
Configuration config = WebConfigurationManager.OpenWebConfiguration(configPath);
ProfileSection section = (ProfileSection)config.GetSection("system.web/profile");
ProviderSettingsCollection settings = section.Providers;
NameValueCollection membershipParams = settings[section.DefaultProvider].Parameters;
Initialize(section.DefaultProvider, membershipParams);
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
if (!string.IsNullOrEmpty(instance))
{
// Update the private connection string field in the base class.
string connectionString = "";//my connection
// Set private property of Membership provider.
FieldInfo connectionStringField = GetType().BaseType.GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
}
}
the difference betwen my CustomProfileProvider is that I can't use itself because the "create" method is in the ProfileBase. And with ILSpy I have seen a singleton and I wonder if it's not the source of the problem.
The issue is that I only pass one time in the initialize method. I can't do another time to change the datasource.
I hope you can understand my poor english and can help me.
I find a - bad - solution
In the CustomProfileBase class I add some code to change the connectionstring of the singleton instance of the class.
public class AccountProfile : ProfileBase
{
string connectionString = myconnstring;
//1st call not really use but allow to get an instance of my custom AccountProfile
AccountProfile test = (AccountProfile)(ProfileBase.Create(userName));
//change connectionstring oh the instance
FieldInfo connectionStringField = test.Providers["AspNetSqlProfileProvider"].GetType().BaseType.GetField("_sqlConnectionString", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
connectionStringField.SetValue(test.Providers["AspNetSqlProfileProvider"], connectionString);
//new call on the good datasource
return (AccountProfile)AccountProfile.Create(userName);
}
It's not the most beautifull solution by it's work.
What do you think of t ?

ADAM and Azman with ASP.Net forms authentication

Has anyone been able to make ADAM/Azman work with ASP.Net forms authentication. The default ADAM role provider works only with AD Domain users. And every single article I have read says that you need to write a custom role provider for it.
I have also found out bits and pieces of custom role provider code here and there, but nothing concrete. If someone can share the roleprovider needed for this, that will be great.
I have followed following articles so far :
Custom Role provider (doesn't work) - http://www.codeproject.com/KB/aspnet/active_directory_roles.aspx
Partial Custom Role provider code - http://blogs.msdn.com/b/azman/archive/2006/05/06/591230.aspx
Partial Custom Role provider code again - http://blog.avanadeadvisor.com/blogs/johanr/archive/2009/01/20/12373.aspx
MS Article steps to setup ADAM and use it with ASP.Net (windows auth)
Getting started with ADAM for authentication (no roles) - http://www.alexthissen.nl/blogs/main/archive/2007/07/26/getting-started-with-adam-and-asp-net-2-0.aspx
I have a hacked version, and I seriously mean hacked. I don't need to modify roles in my app, so I only implemented 2 methods. I had to send a username and password to query the directory. Someday I'd like to figure out how to use the ActiveDirectoryMembershipProvider's connection string, but I did not spend a lot of time with it, that would simplify things.
public class ActiveDirectoryFormsRoleProvider : RoleProvider
{
public string DomainController { get; set; }
public string ConnectionLDAPSuffix { get; set; }
public string ConnectionUserName { get; set; }
public string ConnectionPassword { get; set; }
public override string ApplicationName { get; set; }
public override bool IsUserInRole(string username, string roleName)
{
var roles = GetRolesForUser(username);
return roles.Contains(roleName);
}
public override string[] GetRolesForUser(string username)
{
var results = new List<string>();
using (var context = new PrincipalContext(ContextType.Domain, DomainController,ConnectionLDAPSuffix,ConnectionUserName,ConnectionPassword))
{
try
{
var p = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);
//looping twice because I was getting AppDomainUnloadedException on 50% of the first attempts
for (var i = 0; i < 2; i++)
{
try
{
var groups = p.GetAuthorizationGroups();
foreach (GroupPrincipal group in groups)
{
var name = group.SamAccountName;
if (!string.IsNullOrWhiteSpace(name))
results.Add(group.SamAccountName);
}
break;
}
catch (AppDomainUnloadedException)
{
}
}
}
catch (Exception ex)
{
throw new ProviderException("Unable to query Active Directory.", ex);
}
}
return results.ToArray();
}
...
For some reason on my production server, I have to make 2 attempts of GetAuthorizationGroups() because 50% of the time the first attempt failed by throwing AppDomainUnloadedException. You might be able to remove that for loop.
And here is my web.config element:
<roleManager enabled="true" defaultProvider="ActiveDirectoryFormsRoleProvider">
<providers>
<clear />
<add name="ActiveDirectoryFormsRoleProvider"
type="myapp.ActiveDirectoryFormsRoleProvider"
applicationName="myapp"
DomainController="domaincontroller.testdomain.corp"
ConnectionLDAPSuffix="DC=testdomain,DC=corp"
ConnectionUsername="username"
ConnectionPassword="password"
/>
</providers>
</roleManager>

Resources