1

I have a problem, so I thought I would come to the brightest minds on the web.

I have written an ASP.NET MVC application that interfaces with a web service provided by another application. My app basically just adds some features to the other web application.

Both applications have a database. I am trying to limit the configuration for my application by using the other applications SQL Server credentials. This is so that if they decide to change the password for the other application, mine will just start working.

These credentials are saved in a .DSN file that my application can reach. How can I get my application, which uses Entity Framework, to use a connection string that is created from the details read in the .DSN file?

I can figure out the code to read the .DSN file, so if you wish to provide some code examples you can base them around setting the connection string for EF.

I am also open to other solutions, or even reasons why I shouldn't do this.

Thanks in advance.

PS. As I was writing this, I came up with a little concept. I am going to test it out now to see how it goes. But here is the basics:

  1. On start up, read the needed details into static properties.
  2. public MyContext() : base(getConnectionString()) { }

3.

private SomeObjectTypeHere getConnectionString()
{
   //read static properties
   //return .....something..... not sure yet....
}

Thoughts on that maybe?

EDIT I have created a method that reads the .DSN file and gets the server, the user id and the password. I now have these stored in static properties. In my context, how can I set my connection string now that i have the required details.

spovelec
  • 369
  • 1
  • 4
  • 20
  • I doubt that EF supports SQL Server DSN, why not trying to provide server credentials directly? And have you using DB First to connect with other server instance? – Tetsuya Yamamoto Sep 05 '17 at 00:51
  • Yeah, EF doesn't support DSN. The reason that I don't want to apply the credentials directly is that I don't want to have to store them. I just want to use the same user that the other application uses. – spovelec Sep 05 '17 at 00:54
  • 1
    Sounds like you want users access the different DBs using each credentials (i.e. dynamically generated connection string), is it right? I think you can pass DB connection string to constructor of entity partial class. – Tetsuya Yamamoto Sep 05 '17 at 00:57
  • Yeah, sort of. I never want to access their database, but I also don't want to have to have users change the password in too many places if they ever need to change it. I want my application to look as integrated as possible. – spovelec Sep 05 '17 at 01:00
  • I suggest you drop DSN approach and just use `SqlClient` connection string. You can utilize `SqlConnectionStringBuilder` or `EntityConnectionStringBuilder` to generate credentials based from another app's DB, then use it on EF. If other DB connection string stored in a file, read the file & generate using two instances mentioned previously. – Tetsuya Yamamoto Sep 05 '17 at 01:07
  • @TetsuyaYamamoto Yeah, that was the plan so far. So you don't see a problem with me reading the .DSN, populating some static properties and then building the strings based on those properties, like in my concept at the end? – spovelec Sep 05 '17 at 01:12
  • SQL Server DSN's do not store passwords. System DSN's are stored in the registry. If the DSN is defined on your web server, and your web app has rights to read the registry, you can read all DSN info from the registry, but you will not be able to read the password from there as it is not stored there. Here are a couple of examples of reading DSN's in C#. https://stackoverflow.com/questions/562016/listing-odbc-data-sources-in-c-sharp – Nick.Mc Sep 05 '17 at 01:17
  • @Nick.McDermaid, this one does. – spovelec Sep 05 '17 at 01:19
  • If you say so. Is it a System DSN or file ODBC entry? You'll need to clarify further in order for anyone to suggest anything. Where exactly is this info stored? – Nick.Mc Sep 05 '17 at 01:20
  • @Nick.McDermaid Even file DSN doesn't have password in its credentials. It only stores UID & database name. To read file DSN is just like reading a text file and store extracted strings in respective properties, then pass the property to `SqlConnectionStringBuilder`. – Tetsuya Yamamoto Sep 05 '17 at 01:21
  • I said a .DSN File. So, to me at least, this means that there is a file with a .DSN extension. – spovelec Sep 05 '17 at 01:22
  • Have you opened the file and identified the components in it yet? As already mentioned you just need to use standard file operations to open it and load the contents. But your IIS identity needs rights to do that – Nick.Mc Sep 05 '17 at 01:23
  • @Nick.McDermaid, I did say that I would be able to read the DSN file. I have added an edit to say that I have now done this. – spovelec Sep 05 '17 at 01:26
  • 1
    Sorry I misread most of your post there. So really I guess you just need to use `SqlConnectionStringBuilder` to put the pieces together – Nick.Mc Sep 05 '17 at 01:27
  • 1
    As I mentioned before, you can create new instance of `SqlConnectionStringBuilder` like `var connectionString = new SqlConnectionStringBuilder() { ... }`. Then pass generated connection string into constructor of EF's entity (provide what method created to store credentials here). – Tetsuya Yamamoto Sep 05 '17 at 01:31

1 Answers1

1

So, the biggest issue that I was really having was how to set my connection string in Entity Framework. But I was also hoping that maybe someone else had worked with .DSN files.

Anyway, here was my solution. Still looking for problems that might arise from this, so if you can see any issues, let me know!

First, I created a method that was run on startup. This method ran through the .DSN file and picked out the gems.

Keep in mind that I have never worked with .DSN files, and the section that gets the password is unique to my situation.

            var DSNFileContents = File.ReadAllLines(WebConfigurationManager.AppSettings["AppPath"] + @"\App.DSN");//reads DSN into a string array

            //get UID
            string uid = DSNFileContents.Where(line => line.StartsWith("UID")).First().Substring(4);//get UID from array
            //test if uid has quotes around it
            if (uid[0] == '"' && uid[uid.Length - 1] == '"')
            {
                //if to starts with a quote AND ends with a quote, remove the quotes at both ends
                uid = uid.Substring(1, uid.Length - 2);
            }

            //get server
            string server = DSNFileContents.Where(line => line.StartsWith("SERVER")).First().Substring(7);//get the server from the array
            //test if server has quotes around it
            if (server[0] == '"' && server[server.Length - 1] == '"')
            {
                //if to starts with a quote AND ends with a quote, remove the quotes at both ends
                server = server.Substring(1, server.Length - 2);
            }

            //THIS WON'T WORK 100% FOR ANYONE ELSE. WILL NEED TO BE ADAPTED
            //test if PWD is encoded
            string password = "";
            if (DSNFileContents.Where(line => line.StartsWith("PWD")).First().StartsWith("PWD=/Crypto:"))
            {
                string secretkey = "<secret>";
                string IV = "<alsoSecret>";
                byte[] encoded = Convert.FromBase64String(DSNFileContents.Where(line => line.StartsWith("PWD")).First().Substring(12));
                //THIS LINE IN PARTICULAR WILL NOT WORK AS DecodeSQLPassword is a private method I wrote to break the other applications encryption
                password = DecodeSQLPassword(encoded, secretkey, IV);
            }
            else
            {
                //password was not encrypted
                password = DSNFileContents.Where(line => line.StartsWith("PWD")).First().Substring(4);
            }

            //build connection string
            SqlConnectionStringBuilder cString = new SqlConnectionStringBuilder();
            cString.UserID = uid;
            cString.Password = password;
            cString.InitialCatalog = "mydatabase";
            cString.DataSource = server;
            cString.ConnectTimeout = 30;
            //statProps is a static class that I have created to hold some variables that are used globally so that I don't have to I/O too much.
            statProps.ConnectionString = cString.ConnectionString;

Now that I have the connection string saved, I just have my database Context use it as below,

public class myContext : DbContext
{
    public myContext() : base(statProps.ConnectionString) { }

    //all my DbSets e.g.
    public DbSet<Person> Persons{ get; set; }


}

This is simple, yes, but I hoping that it can provide some information to anyone that was looking to do something similar but was not sure about how it should be handled.

Again, let me know if you like or dislike this solution and if you dislike it, what is your solution and why.

Thanks again!

spovelec
  • 369
  • 1
  • 4
  • 20