Thursday, July 15, 2004

Today has indeed been a somber day.  Today just before 4:00 PM my beloved grandmother (Granny) passed away.  Up until just a few days ago she's been as active as ever, only just recently a dormant cancer finally caught up to her.  Granny was a wonderful person, a devoted mother and grandmother, and  genuine saint.  Granny leaves behind an incomparable legacy.  We were planning on traveling down to Las Cruces, NM tomorrow to see her as we knew she was ill, but as fate would have it that was not to be.  Instead, we will be traveling down on Sunday for the Tuesday funeral.

I feel genuinely blessed to have known Granny as well as I did - I have many wonderful memories taking care of her garden, playing her piano, doing puzzles with her, and hanging out.  I vividly recall spending (almost) every Sunday evening at her house while growing up.  There we would eat her famous beans, Pa would pop popcorn, and we'd watch Laurence Welk, The Wonderful World of Disney, and Mutual of Omaha's Wild Kingdom.  We would play in the backyard, climb her trees, and build forts and haunted houses in the basement.

I had the privilege of going down to Las Cruces with my previous company in January.  That turned out to be my farewell visit and I am very grateful for it.

Granny and Pa had five children - 4 daughters and a son.  Twenty seven grandchildren and I don't know how many great-grandchildren.  At our family reunion last November we had 96 people there - direct descendants and their spouses - and not quite everyone was accounted for.  What a wonderful experience.

Granny, we love you and are deeply honored to have you as our grandmother.  Your devotion to family and church was amazing.  You will be remembered for so many things: your gardens, your wonderful Thanksgiving dinners, your charity, your beans, your basement where we built many a haunted house, your puzzles, your backyard, your incomparable quilts, and much, much more.  But most of all you will be remembered for being our Granny.  We love you very much.

Thursday, July 15, 2004 4:50:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, July 12, 2004

Back in the days of being a trainer, I used to stand all day long.  Back in those days I was accustomed to it.  I'm spending all day in the booth and while it is great and exciting to talk to people about our killer app my feet are dying.  Note to self: next time at a conference wear shoes that not only fit and aren't a 1/2 size too small, but are comfortable also.

Monday, July 12, 2004 3:30:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

Here I am, continuing in Toronto at the Microsoft Worldwide Partner Conference.  This has been awesome!  While I have a hard time with the 'sales' side of the house (being almost exclusively dev) it is awesome to see the interest and rallying support around our system.  Our web product is receiving rave reviews from customers as well as some very high marks from the CRM team.  We met with Jason Hunt (Technical Product Manager for CRM) and he loves the product.  Additionally, we've met with Brenda Smestad, Barry Givens (who gives us very high reviews and lots of kudos), Graham Sheldon, and others.  It has been awesome!

I've also continued to run into people that I know - which is great!  Today I saw (though he didn't see me) a great friend Dan Peay of Microsoft.  I hope to run into him later and catch up.  Also ran into Michael Reagan (now with Microsoft New York) - what a great guy!  I really remember having a blast with these guys from MS and it really means a lot to me that they not only remember me but have come back to talk and recall the good ol' days.  These are good times!

Monday, July 12, 2004 5:17:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, July 10, 2004

Well, my conference here in Toronto is, to say the least, has been eventful.  To start off, I owe a lot of thanks to customs in Canada.  I didn't realize upon coming that I needed a passport.  It may have been pure dumb luck or whatever, but before coming I remembered to grab mine.  Unfortunately, my passport has been expired for 3 years.  I got a big fat warning prior to leaving that if customs didn't want to let me in the country, I'd spent the night in a cell and be deported.  Needless to say, this had me a little worried upon entering the country.  Well, when I nonchalantly handed my passport and papers to customs the lady (a mighty severe-looking woman I might add) gave my passport a double check, thought about it for a bit, and then just let me in! Whew!  She only asked me what I was in the country for.  That was a relief.

I'm pretty excited to be here at the Microsoft Worldwide Partner Conference.  Though it's very sales-oriented (something that is very hard for me to deal with), I'm having a good time.  We spent the day setting up our booth and meeting with MS partners.  There is a lot of interest revolving around product configurators which is very reassuring and exciting.  I see lots of potential with our product and am extremely excited about our future prospects.

Upon making preparations for this trip/conference I suspected that I might run into friends here - I always do when I come to conferences.  As it turns out my luck still runs hot!  Today I ran into two good friends and associates from my days at Microsoft: Larry Shaw and John Herman.  Additionally I encountered Kristi Schwartz.  It was pretty invigorating to see my good friends.  I hope to see them at the booth to show them what I've been working on and catch up on old times.

Looking forward to a good conference!

Saturday, July 10, 2004 4:19:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, July 08, 2004

I'm very excited to be going to Toronto this week.  I fly out first thing in the morning and I'll be heading to the Microsoft Worldwide Partner Conference.  We've been working feverishly the last several weeks to make sure that everything is ready and in order.  I'm going as a member of Experlogix, Inc.  Aside from the conference, I'm quite thrilled to be traveling to Toronto - I've never been there.  I hope I can get out and see a few cool places, but the schedule will be packed so we'll have to see.

Unfortunately, due to all of this travel, I had to miss the Utah .NET User Group meeting today.  I've heard from my fellow leadership that the meeting was great!  Scott Thornock presented on the topic of Smart Client apps and threw in some WSE 2.0 ...very exciting stuff!  I really wish I could have been there.  Thanks, Scott, for all your efforts!

I've also been away from my family a lot lately which is definitely not the most enjoyable circumstance.  I miss them very much.  When I arrive home next Wednesday we'll have a lot of catching up to do.

Sorry if this seems like rambling, because, well, it is.  It's 2:00 AM (I've been up until 3:30-4:30 every night this week and up at 8:00 so I'm a bit wiped out and a bit scrambled, but we've gotten the majority of things resolved for the conference...<fingersCrossed>the rest is cake!</fingersCrossed>.

Thursday, July 08, 2004 7:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, July 07, 2004
I cannot conceivably conclude to concur that you can't use 'can not' in a query.  Nor can one cater to those that coax coders to create crafty quotes.  Can't we all concede that cannot is the correct way to convey can not, which is wrong?
Wednesday, July 07, 2004 6:34:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I realize that's a pretty boring title, but let me explain:

I have a few websites that I've created over the past several years that I use quite frequently.  These include:

  • A bug tracking system that I developed a few years ago that I'm in the process of overhauling
  • A custom media server that streams audio content based off of custom, user-defined playlists from my personal collection of WMA and miscellaneous files
  • A special family gallery of photographs

Needless to say I am not the only user of these sites.  Various friends, associates, and family members frequent the sites (despite their work-in-progress nature).  However, there is a catch:  anonymous access is not allowed.  If a user wants to view the contents of a site, he must log in.  With the myriad of individuals out there that access these sites it would be impractical, not to mention extremely labor-intensive and would expose my network to security risks to provide a Windows logon for each user...that would be insanity.

For these and other reasons I opted for a Forms-over-SSL-based authentication mechanism to gain access to these sites.  The forms authentication backends ultimately to a SQL Server database that maintains the users and their encrypted credentials.  Ok, nothing magic here.  Simple, routine computing.  But this is where the fun begins.

A user may have access to one, two, or all of the sites.  I don't want to store multiple credentials for the same user (potentially with all the same values), one for each site.  Heaven forbid that we create a user credentials database for each site!  I wanted a single repository for all credentials.  Ok, still nothing magic - everyone has to solve these kinds of problems.  Despite these relatively mundane and trivial tasks, the whole reason I bring this up is because I think my solution is pretty cool.  Note: I haven't yet tested this solution for scalability nor have I profiled it.  I'm not expecting a boatload of traffic, so this suits my personal needs fine, but I would test this for any real-world application...don't just take my word for it that it's good.

On the backend I have a SQL Server 2000 database that maintains a master list of websites and logins and mappings between the two among some other stuff.  Stored procedures exist that allow for the management of these tables as well as the validating that a user has permissions to a particular site.

Over this database exists a set of web services that make calls to the stored procedures.  These web services (by their very nature) are accessible to the web sites but instead of accessing them directly, the web sites rely on a set of helper classes that abstract the web service interfaces.  For security reasons, the web services are hidden completely behind a firewall with no outside access.  Additionally, the only user with access to the database procedures (and only the procedures) is the user associated with the web service virtual directory in IIS.

An example of the web service call that validates the user is the following:

[WebMethod(Description="Checks to determine if the login id and password are associated with a particular website.")]
public bool IsValidLogin(string loginID, string password, string webSite) {
   string pwdHash = FormsAuthentication.HashPasswordForStoringInConfigFile(password, "SHA1");

   using ( SqlCommand cm = new SqlCommand("dsProc_ssoIsValidLogin", getConnection()) ) {
      cm.CommandType = CommandType.StoredProcedure;
      cm.Parameters.Add("@LoginID", SqlDbType.NVarChar, 25).Value = loginID;
      cm.Parameters.Add("@Password", SqlDbType.NVarChar, 40).Value = pwdHash;
      cm.Parameters.Add("@Website", SqlDbType.NVarChar, 30).Value = webSite;
      return (int)cm.ExecuteScalar() > 0;
   }
}

As can be ascertained, each website to which the users can gain access has a name.  The userid/password combination is matched up to the website name.  In the case of a match, a value of True is returned.

I mentioned a second ago that there is a set of helper classes that provide the interfaces to the Web Services.  Now with Visual Studio .NET I could have just as easily created a web reference and called it good.  However, I don't know if you may have had the same experiences that I've had, but I tend to open the References.cs file (which is automatically generated when the web reference is created) and edit it.  Mainly the edits involve the line that reads

this.Url = "http://localhost/.....";

because this url differs between development and production.  I usually edit it to read the value from a configuration file (Web.config).  It's a pain to have to edit that file only to have to reedit it when the web reference is refreshed - it's easy to forget to do it, that's for sure.  Not that my web services change their contracts often but you know.

That's one of the reasons I decided to create the set of helper classes, but the main driving reason was that I wanted a consistent, simplified, and shared object that didn't behave like a web service.  I didn't want to have to create an instance of the soap client to call a method and then dispose of it.  I wanted this class to be sharable across all of the web sites that needed the same functionality.

The solution I came up with was to create a class called SignOnValidator that has a single static method called IsValidLogin that takes the user's login id and password as parameters.  This method then resolves the name of the website by reflecting on the calling assembly to find a custom attribute called SignOnWebSiteNameAttribute instead of relying on a parameter passed in.  Then it creates an instance of an internal class that manages the call to the web service proper, returning the result.  I might need to do some optimization to the code, but here it is (unoptimized):

using System;
using System.Configuration;
using System.Reflection;
using System.Web.Services;
using System.Web.Services.Protocols;

[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyCopyright("Copyright © 2004, Devstone Software")]

namespace Devstone.Web.WebAuth {

   public sealed class SignOnValidator {

      private SignOnValidator() { /* no public constructor */ }

      public static bool IsValidLogin(string loginID, string password) {
         string siteName;

         // get the instance of the attribute in the calling assembly
         Assembly caller = Assembly.GetCallingAssembly();
         if ( caller.IsDefined(typeof(SignOnWebSiteNameAttribute), false) ) {
            // retrieve the SignOnName for the calling application
            SignOnWebSiteNameAttribute attrib = caller.GetCustomAttributes(typeof(SignOnWebSiteNameAttribute), false)[0] as SignOnWebSiteNameAttribute;
            siteName = attrib.SiteName;
         }
         else {
            throw new ArgumentException("Unable to authenticate the user.  Please make sure that the calling assembly has the SignOnWebSiteNameAttribute specified.");
         }

         using ( SignOnWebService svc = new SignOnWebService() ) {
            return svc.IsValidLogin(loginID, password, siteName);
         }
      }

   } // SignOnValidator class


   [WebServiceBinding(Name="SignOnValidatorSoap", Namespace="
http://signon.devstone.com/")]
   internal sealed class SignOnWebService : SoapHttpClientProtocol {
      internal SignOnWebService() {
         string url = ConfigurationSettings.AppSettings["LogonServiceUrl"];
         if ( null == url )
            throw new ArgumentException("Unable to resolve the URL for the logon service.  Please make sure your application's configuration settings are correct.");
         else
            this.Url = url;
      }

      [SoapDocumentMethod("
http://signon.devstone.com/IsValidLogin",
       RequestNamespace="
http://signon.devstone.com/",
       ResponseNamespace="
http://signon.devstone.com/",
       Use=System.Web.Services.Description.SoapBindingUse.Literal,
       ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
      public bool IsValidLogin(string loginID, string password, string webSite) {
         object[] results = this.Invoke("IsValidLogin", new object[] { loginID, password, webSite});
         return ((bool)(results[0]));
      }

   } // SignOnWebService class

// Devstone.Web.WebAuth namespace

Then of course there's the question of the SignOnWebSiteNameAttribute.  Each website that utilizes this sign on mechanism will have to define this attribute, but in my book this simplifies management of the site names.

[assembly: SignOnWebSiteName("mysitename")]

Done.  Everything else will be taken care of because the helper class will automatically resolve the proper site name on invocation.  Oh, and if you're wanting the code for the attribute class, here it is:

using System;

namespace Devstone.Web.WebAuth {

  
[AttributeUsage(AttributeTargets.Assembly)]
   public sealed class SignOnWebSiteNameAttribute : Attribute {
      private string _siteName = null;

      public SignOnWebSiteNameAttribute(string siteName) {
         _siteName = siteName;
      }

      public string SiteName {
         get { return _siteName; }
      }
   } // SignOnWebSiteNameAttribute class

// Devstone.Web.WebAuth namespace

I really like this solution despite the fact that I have to maintain changes made in the web service manually in the SignOnValidator class.  Apart from everything else, I found a good example for using a custom Attribute!  I really think this will simplify the management of website users while providing a very simple programming model for validating users.  Not to mention the benefit that a user has a single login id, name, password, and email across all sites - I hate trying to remember 50 million user IDs and passwords

Wednesday, July 07, 2004 3:34:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, July 05, 2004

I don't think that I'm unique in this experience (I've spoken with others that have the same experience), but have you ever solved some of your most difficult problems in your sleep?  It happens repetitively to me.  Time after time, especially after slaving away and not getting anywhere on an issue (primarily code issues) I solve the problem in the middle of the night while I'm sleeping.  I hate it when I wake up, fully aware that the problem was solved, but am unable to recall the solution - it's infuriating!

Case in point: I am working with a friend from California on an ASP.NET product and am banging my head against some pretty intense issues.  Just the other night I was (while sleeping) running into the issue yet again, but solved it rapidly.  When I awoke shortly thereafter I knew the problem had been solved but couldn't remember.  But this morning while working on it came to solve the problem and had a deja vú moments and the dream came back to me.

Maybe that's why I like to sleep as much as I do.  Or perhaps it's my body telling me that it's time to solve problems and it wants to shut down.  I guess I should listen to it more often rather than kill myself working into the wee hours of the morning.

Monday, July 05, 2004 7:56:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, July 04, 2004

On this day, the United States of America's Independence Day, I am overcome with gratitude for those that sacrificed so much for the country in which I live.  What a wonderful, blessed country!  It is because of their willingness to place their beliefs and even their very lives on the line that I live in such a beautiful place.  I commit to do what I can to live up to their legacy.

Sunday, July 04, 2004 2:25:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback