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

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();
}
}

Related

How to work with an DevArt edml file and start a connection?

I am working with the newest DevArt Oracle version and created a EDML file that connects to my Oracle 12 database and get the models with the db first approach.
I followed this howto:
https://www.devart.com/entitydeveloper/docs/
So I have my context and my model auto generated:
public partial class KiddataAdminEntities : DbContext
{
#region Constructors
/// <summary>
/// Initialize a new KiddataAdminEntities object.
/// </summary>
public KiddataAdminEntities() :
base(#"name=KiddataAdminEntitiesConnectionString")
{
Configure();
}
/// <summary>
/// Initializes a new KiddataAdminEntities object using the connection string found in the 'KiddataAdminEntities' section of the application configuration file.
/// </summary>
public KiddataAdminEntities(string nameOrConnectionString) :
base(nameOrConnectionString)
{
Configure();
}
private void Configure()
{
this.Configuration.AutoDetectChangesEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.ValidateOnSaveEnabled = true;
}
#endregion
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Anrede> Anrede { get; set; }
}
Now I try to get it to work in my main in another project (just a simple console application with a start.cs):
KiddataAdminEntities context = new KiddataAdminEntities("User Id=xxxx;Password=xxxx;Server=xx;Direct=True;Sid=xxxx;Persist Security Info=True");
var listOfAnrede = context.Anrede.ToList();
So now I get the error "Keyword user id not supported".
I googled this and I found out that problably EF6 is trying to get a default connection, not an oracle connection with DevArt...
I tried to play with the app.config in different ways but it didnt help.
Now I tried to create my own connection with the DevArt.Data.Oracle provider, like shown here:
https://www.devart.com/dotconnect/oracle/articles/tutorial-connection.html
OracleConnection oc = new OracleConnection();
oc.ConnectionString = constring2;
oc.Open();
var test = oc.ServerVersion;
This works fine, so the connectionstring is okay, but still I can't put these two together. I tried to overload the constructor so I can put in my Connection:
public KiddataAdminEntities(DbConnection con, bool contextOwnsConnection)
: base(con, contextOwnsConnection)
{
}
Then I got the error on
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
That I should not do that...
If you are using XML mapping with Devart Entity Model (*.edml), try this code:
using Devart.Data.Oracle;
using System.Data.EntityClient;
...
OracleConnectionStringBuilder oracleBuilder = new OracleConnectionStringBuilder();
oracleBuilder.UserId = "...";
oracleBuilder.Password = "...";
oracleBuilder.Server = "...";
oracleBuilder.Direct = true;
oracleBuilder.Sid = "...";
oracleBuilder.PersistSecurityInfo = true;
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
entityBuilder.Provider = "Devart.Data.Oracle";
entityBuilder.ProviderConnectionString = oracleBuilder.ConnectionString;
entityBuilder.Metadata = #"res://*/MyModel.csdl|res://*/MyModel.ssdl|res://*/MyModel.msl";
using (Entities context = new Entities(entityBuilder.ToString())) {
var a = context.MyEntity.First();
}
Refer to
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/how-to-build-an-entityconnection-connection-string
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/connection-strings
FYI, you can generate fluent mapping (instead of XML mapping). For this, disable a predefined EntityObject template, enable the DbContext template and set the options:
Fluent Mapping=True in the properties of DbContext template
Metadata Artifact Processing=Do Not Generate Mapping Files in the properties of EntityContextModel

WebApi HttpClient per request per new connection in pool

In my case, I make web request to server side by HttpClient. But every time, there will be a new connection in the connection pool. The program only used one connection string. The new connection goes up too quickly, and then exceeds the max pool size 100. I have to inverstigate the issue about the database connection pool and IIS.
sqlserver database connection string:
<connectionStrings>
<add name="WERP2ConnectionString" connectionString="Data Source=localhost;Initial Catalog=WERP2-1108;User ID=sa;Password=sasa;Connect Timeout=15;Encrypt=False;"/>
</connectionStrings>
client program:
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("api test: {0}", i);
ApiTest(); //to mimic the 5 different users make the api request.
}
Console.ReadLine();
}
private static void ApiTest()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost/StressApi/");
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync("api/order/GetOrder").Result;
if (response.IsSuccessStatusCode)
{
var message = response.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(message))
{
Console.WriteLine(message);
}
}
else
{
Console.WriteLine("Error Code" +
response.StatusCode + " : Message - " + response.ReasonPhrase);
}
//Console.ReadLine();
}
}
WebApi controller:
public class OrderController : ApiController
{
[HttpGet]
public HttpResponseMessage GetOrder()
{
OrderModel model = new OrderModel();
var entity = model.GetByID();
HttpResponseMessage response = Request.CreateResponse<OrderEntity>(HttpStatusCode.OK, entity);
return response;
}
}
public class OrderEntity
{
public int ID { get; set; }
public string OrderCode { get; set; }
public int OrderType { get; set; }
}
public class OrderModel
{
public OrderEntity GetByID()
{
OrderEntity entity = new OrderEntity();
string sql = #"select * from salorder where id=97";
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["WERP2ConnectionString"].ToString()))
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
// don't forget to actually open the connection before using it
conn.Open();
try
{
using (SqlDataReader dataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
while (dataReader.Read())
{
// do something
Console.WriteLine(dataReader.GetValue(0) + " - " + dataReader.GetValue(1) + " - " + dataReader.GetValue(2));
entity.ID = int.Parse(dataReader.GetValue(0).ToString());
entity.OrderCode = dataReader.GetValue(1).ToString();
entity.OrderType = int.Parse(dataReader.GetValue(2).ToString());
//dataReader
}
}
}
finally
{
//SqlConnection.ClearPool(conn);
}
}
return entity;
}
}
Every sql query will make a process in sqlserver, we can find them in SQL SERVER Activity Monitor, and there are 5 records, becuase I repeat 5 times of the query.
If we use SP_WHO command, we also can verify the 5 process records, and they are in sleeping and AWAITTING COMMAND state.
I am confused that how I can avoid this connection leak issue. Although I make the same sql query every time, there are still new connection regenerated. And I have tested that these connection will be recycled about 6min20s. Of course I could reset IIS or make application pool recycled to free them. But this definitely cannot be accepted in the on line system. please someone can make any suggestoins? Thanks.
By the way, the programming is run with .NET Framework 4.0, IIS7 and SQLSERVER2008 R2.
HttpClient will open a new socket with each request. So it is recommended to use a single instance of this .
For more information go to below link
https://www.codeproject.com/Articles/1194406/Using-HttpClient-As-It-Was-Intended-Because-You-re

ASP.NET Membership for one website but multiple and potentially unknown sql server instance

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

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.

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