Monday, January 11, 2010

DNN Authentication Module

When trying to write an authentication module for a custom data store or custom authentication system (i.e. LDAP, CAS, Active Directory, etc.), I’ve found that a lot of the examples people have done are lacking in some way. The DNN system that I use needs to be able to authenticate users against an LDAP. Since my system is also a collection of multiple portals, I also need our authentication system to allow people who have accounts in other portals to automatically be added as a valid user to another one they sign-in to without having to enter a password. The last part is difficult because the default design of DNN makes it difficult to have one user span multiple portals. I discovered that in order to get it to work properly, you have to bypass some of the DNN API’s and go straight to the data providers. First, I created a class to represent the custom configuration of the module:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using DotNetNuke.Common.Utilities;
   6: using DotNetNuke.Entities.Portals;
   7: using DotNetNuke.Services.Authentication;
   8:  
   9: namespace DotNetNuke.Authentication.RouteY
  10: {
  11:     /// <summary>
  12:     /// This class is used to store settings for the RouteY authentication provider
  13:     /// </summary>
  14:     [Serializable]
  15:     public class RouteYAuthConfig : AuthenticationConfigBase
  16:     {
  17:         #region Constants
  18:  
  19:         private static readonly string CACHEKEY = "Authentication.RouteY";
  20:         private static readonly string KEY_ENABLED = "ROUTEY_Enabled";
  21:         private static readonly string KEY_LDAPURL = "ROUTEY_LDAPUrl";
  22:         private static readonly string KEY_LDAPUSERDNFORMAT = "ROUTEY_UserDNFormat";
  23:         private static readonly string KEY_LDAPUSERNAME = "ROUTEY_Username";
  24:         private static readonly string KEY_LDAPPASSWORD = "ROUTEY_Password";
  25:         private static readonly string KEY_LDAPSTARTINGDN = "ROUTEY_StartedDN";
  26:         private static readonly string KEY_AUTOREGISTER = "ROUTEY_AutoRegister";
  27:  
  28:         #endregion
  29:  
  30:         #region Properties
  31:  
  32:         /// <summary>
  33:         /// Gets or sets the value that enables Route Y authentication
  34:         /// </summary>
  35:         public bool Enabled
  36:         {
  37:             get;
  38:             set;
  39:         }
  40:  
  41:         /// <summary>
  42:         /// Gets or sets the value indicating the location of the LDAP server
  43:         /// </summary>
  44:         public string LDAPUrl
  45:         {
  46:             get;
  47:             set;
  48:         }
  49:  
  50:         /// <summary>
  51:         /// Gets of sets the value indicating the format a user DN should be in. Should
  52:         /// conform to C# string formats
  53:         /// </summary>
  54:         public string LDAPUserDNFormat
  55:         {
  56:             get;
  57:             set;
  58:         }
  59:  
  60:         /// <summary>
  61:         /// Gets or sets the username by which the LDAP can be accessed
  62:         /// </summary>
  63:         public string LDAPUsername
  64:         {
  65:             get;
  66:             set;
  67:         }
  68:  
  69:         /// <summary>
  70:         /// Gets or sets the password by which the LDAP can be accessed
  71:         /// </summary>
  72:         public string LDAPPassword
  73:         {
  74:             get;
  75:             set;
  76:         }
  77:  
  78:         /// <summary>
  79:         /// Gets or sets the starting place where users will be searched for
  80:         /// </summary>
  81:         public string LDAPStartingDN
  82:         {
  83:             get;
  84:             set;
  85:         }
  86:  
  87:         /// <summary>
  88:         /// Gets or sets the flag allowing auto user registration on login
  89:         /// </summary>
  90:         public bool AutoRegister
  91:         {
  92:             get;
  93:             set;
  94:         }
  95:  
  96:         #endregion
  97:  
  98:         #region Constructor
  99:  
 100:         /// <summary>
 101:         /// Initalizes an instance of the route Y configuration settings
 102:         /// </summary>
 103:         /// <param name="PortalID_p"></param>
 104:         protected RouteYAuthConfig(int PortalID_p)
 105:             : base(PortalID_p)
 106:         {
 107:             try
 108:             {
 109:                 string setting = null;
 110:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_ENABLED, out setting))
 111:                     Enabled = Boolean.Parse(setting);
 112:  
 113:                 setting = null;
 114:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_LDAPURL, out setting))
 115:                     LDAPUrl = setting;
 116:  
 117:                 setting = null;
 118:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_LDAPUSERDNFORMAT, out setting))
 119:                     LDAPUserDNFormat = setting;
 120:  
 121:                 setting = null;
 122:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_LDAPUSERNAME, out setting))
 123:                     LDAPUsername = setting;
 124:  
 125:                 setting = null;
 126:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_LDAPPASSWORD, out setting))
 127:                     LDAPPassword = setting;
 128:  
 129:                 setting = null;
 130:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_LDAPSTARTINGDN, out setting))
 131:                     LDAPStartingDN = setting;
 132:  
 133:                 setting = null;
 134:                 if (PortalController.GetPortalSettingsDictionary(PortalID_p).TryGetValue(KEY_AUTOREGISTER, out setting))
 135:                     AutoRegister = Boolean.Parse(setting);
 136:             }
 137:             catch { }
 138:         }
 139:  
 140:         #endregion
 141:  
 142:         #region Static methods
 143:  
 144:         /// <summary>
 145:         /// Clears the given portal configuration from the data cache
 146:         /// </summary>
 147:         /// <param name="PortalID_p"></param>
 148:         public static void ClearConfig(int PortalID_p)
 149:         {
 150:             string key = CACHEKEY + "_" + PortalID_p.ToString();
 151:             DataCache.RemoveCache(key);
 152:         }
 153:  
 154:         /// <summary>
 155:         /// Gets the route y configuration settings for a given portal
 156:         /// </summary>
 157:         /// <param name="PortalID_p"></param>
 158:         /// <returns></returns>
 159:         public static RouteYAuthConfig GetConfig(int PortalID_p)
 160:         {
 161:             string key = CACHEKEY + "_" + PortalID_p.ToString();
 162:             RouteYAuthConfig config = DataCache.GetCache(key) as RouteYAuthConfig;
 163:  
 164:             if (config == null)
 165:             {
 166:                 config = new RouteYAuthConfig(PortalID_p);
 167:                 DataCache.SetCache(key, config);
 168:             }
 169:  
 170:             return config;
 171:         }
 172:  
 173:         /// <summary>
 174:         /// Saves an updated configuration
 175:         /// </summary>
 176:         /// <param name="Config_p"></param>
 177:         public static void UpdateConfig(RouteYAuthConfig Config_p)
 178:         {
 179:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_ENABLED, Config_p.Enabled.ToString());
 180:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_LDAPURL, Config_p.LDAPUrl);
 181:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_LDAPUSERDNFORMAT, Config_p.LDAPUserDNFormat);
 182:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_LDAPUSERNAME, Config_p.LDAPUsername);
 183:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_LDAPPASSWORD, Config_p.LDAPPassword);
 184:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_LDAPSTARTINGDN, Config_p.LDAPStartingDN);
 185:             PortalController.UpdatePortalSetting(Config_p.PortalID, KEY_AUTOREGISTER, Config_p.AutoRegister.ToString());
 186:             ClearConfig(Config_p.PortalID);
 187:         }
 188:  
 189:         #endregion
 190:     }
 191: }

The next thing I did was create some classes to represent a user and also handle authentication with the LDAP.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Collections.Specialized;
   6: using System.Reflection;
   7:  
   8: namespace DotNetNuke.Authentication.RouteY
   9: {
  10:     public class RouteYUser
  11:     {
  12:         [RouteYLDAPField(LDAPField = "ldap-field-name1")]
  13:         public string NetID
  14:         {
  15:             get;
  16:             set;
  17:         }
  18:  
  19:         [RouteYLDAPField(LDAPField = "ldap-field-name2")]
  20:         public string BYU_ID
  21:         {
  22:             get;
  23:             set;
  24:         }
  25:  
  26:         [RouteYLDAPField(LDAPField = "ldap-field-name3")]
  27:         public string Email
  28:         {
  29:             get;
  30:             set;
  31:         }
  32:  
  33:         [RouteYLDAPField(LDAPField = "ldap-field-name4")]
  34:         public string FullName
  35:         {
  36:             get;
  37:             set;
  38:         }
  39:  
  40:         [RouteYLDAPField(LDAPField = "ldap-field-name5")]
  41:         public string GivenName
  42:         {
  43:             get;
  44:             set;
  45:         }
  46:  
  47:         [RouteYLDAPField(LDAPField = "ldap-field-name6")]
  48:         public string FirstName
  49:         {
  50:             get;
  51:             set;
  52:         }
  53:  
  54:         [RouteYLDAPField(LDAPField = "ldap-field-name7")]
  55:         public string LastName
  56:         {
  57:             get;
  58:             set;
  59:         }
  60:  
  61:         [RouteYLDAPField(LDAPField = "ldap-field-name8")]
  62:         public string HomeTown
  63:         {
  64:             get;
  65:             set;
  66:         }
  67:  
  68:         [RouteYLDAPField(LDAPField = "ldap-field-name9")]
  69:         public string Department
  70:         {
  71:             get;
  72:             set;
  73:         }
  74:  
  75:         [RouteYLDAPField(LDAPField = "ldap-field-name10")]
  76:         public string JobTitle
  77:         {
  78:             get;
  79:             set;
  80:         }
  81:  
  82:         [RouteYLDAPField(LDAPField = "ldap-field-name11")]
  83:         public string WorkPhone
  84:         {
  85:             get;
  86:             set;
  87:         }
  88:  
  89:         [RouteYLDAPField(LDAPField = "ldap-field-name12")]
  90:         public string CampusAddress
  91:         {
  92:             get;
  93:             set;
  94:         }
  95:  
  96:         [RouteYLDAPField(LDAPField = "ldap-field-name13")]
  97:         public string LocalPhone
  98:         {
  99:             get;
 100:             set;
 101:         }
 102:  
 103:         [RouteYLDAPField(LDAPField = "ldap-field-name14")]
 104:         public string LocalAddress
 105:         {
 106:             get;
 107:             set;
 108:         }
 109:  
 110:         [RouteYLDAPField(LDAPField = "ldap-field-name15")]
 111:         public string PermanentPhone
 112:         {
 113:             get;
 114:             set;
 115:         }
 116:  
 117:         [RouteYLDAPField(LDAPField = "ldap-field-name16")]
 118:         public string PermanentAddress
 119:         {
 120:             get;
 121:             set;
 122:         }
 123:  
 124:         public NameValueCollection GetProfile()
 125:         {
 126:             NameValueCollection profile = new NameValueCollection();
 127:  
 128:             foreach (PropertyInfo property in GetType().GetProperties())
 129:             {
 130:                 try { profile.Add(property.Name, property.GetValue(this, null).ToString()); }
 131:                 catch { }
 132:             }
 133:  
 134:             return profile;
 135:         }
 136:     }
 137: }
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace DotNetNuke.Authentication.RouteY
   7: {
   8:     [AttributeUsage(AttributeTargets.Property)]
   9:     public class RouteYLDAPField : Attribute
  10:     {
  11:         #region Properties
  12:  
  13:         public string LDAPField
  14:         {
  15:             get;
  16:             set;
  17:         }
  18:  
  19:         #endregion
  20:     }
  21: }
   1: using System;
   2: using System.Collections.Generic;
   3: using System.DirectoryServices.Protocols;
   4: using System.Linq;
   5: using System.Security.Cryptography.X509Certificates;
   6: using System.Text;
   7: using System.Reflection;
   8: using System.ComponentModel;
   9:  
  10: namespace DotNetNuke.Authentication.RouteY
  11: {
  12:     /// <summary>
  13:     /// This class handles authentication against an LDAP server.
  14:     /// </summary>
  15:     public class RouteYAuthentication
  16:     {
  17:         #region Fields
  18:  
  19:         private RouteYAuthConfig m_Config;
  20:  
  21:         #endregion
  22:  
  23:         public RouteYAuthentication(RouteYAuthConfig Config_p)
  24:         {
  25:             m_Config = Config_p;
  26:         }
  27:  
  28:         /// <summary>
  29:         /// Authenticates a user against an LDAP server.
  30:         /// </summary>
  31:         /// <param name="NetID_p">User to authenticate</param>
  32:         /// <param name="Password_p">Their password</param>
  33:         /// <returns></returns>
  34:         public bool AuthenticateUser(string NetID_p, string Password_p)
  35:         {
  36:             if (String.IsNullOrEmpty(Password_p) || String.IsNullOrEmpty(NetID_p))
  37:                 return false;
  38:  
  39:             string userDN = String.Format(m_Config.LDAPUserDNFormat, NetID_p);
  40:             return AuthenticateUser(userDN, Password_p, m_Config.LDAPUrl);
  41:         }
  42:  
  43:         /// <summary>
  44:         /// Finds the given user in the LDAP and returns a user object with information
  45:         /// </summary>
  46:         /// <param name="NetID_p"></param>
  47:         /// <returns></returns>
  48:         public RouteYUser FindUser(string NetID_p)
  49:         {
  50:             try
  51:             {
  52:                 Uri uri = new Uri(m_Config.LDAPUrl);
  53:  
  54:                 using (LdapConnection ldapCon = new LdapConnection(new LdapDirectoryIdentifier(uri.Host, 636)))
  55:                 {
  56:                     ldapCon.Credential = new System.Net.NetworkCredential(m_Config.LDAPUsername, m_Config.LDAPPassword);
  57:                     ldapCon.AuthType = AuthType.Basic;
  58:                     ldapCon.SessionOptions.SecureSocketLayer = true;
  59:                     ldapCon.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(VerifyCertificateCallback);
  60:                     ldapCon.SessionOptions.ProtocolVersion = 3;
  61:                     ldapCon.SessionOptions.RootDseCache = false;
  62:                     ldapCon.Bind();
  63:  
  64:                     SearchRequest searchRequest = new SearchRequest(m_Config.LDAPStartingDN, String.Format("(uid={0})", NetID_p), SearchScope.OneLevel);
  65:                     SearchResponse response = ldapCon.SendRequest(searchRequest) as SearchResponse;
  66:  
  67:                     if (response.Entries.Count > 0)
  68:                     {
  69:                         RouteYUser user = new RouteYUser();
  70:                         FillUserObject(user, response);
  71:                         return user;
  72:                     }
  73:                     else
  74:                         return null;
  75:                 }
  76:             }
  77:             catch (Exception)
  78:             {
  79:                 throw;
  80:             }
  81:         }
  82:  
  83:         #region Private Methods
  84:  
  85:         /// <summary>
  86:         /// Performs the actual authentication again a particular LDAP server
  87:         /// </summary>
  88:         /// <param name="Username_p"></param>
  89:         /// <param name="Password_p"></param>
  90:         /// <param name="DirectoryPath_p"></param>
  91:         /// <returns></returns>
  92:         private bool AuthenticateUser(string Username_p, string Password_p, string DirectoryPath_p)
  93:         {
  94:             try
  95:             {
  96:                 Uri uri = new Uri(DirectoryPath_p);
  97:  
  98:                 using (LdapConnection ldapCon = new LdapConnection(new LdapDirectoryIdentifier(uri.Host, 636)))
  99:                 {
 100:                     ldapCon.Credential = new System.Net.NetworkCredential(Username_p, Password_p);
 101:                     ldapCon.AuthType = AuthType.Basic;
 102:                     ldapCon.SessionOptions.SecureSocketLayer = true;
 103:                     ldapCon.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(VerifyCertificateCallback);
 104:                     ldapCon.SessionOptions.ProtocolVersion = 3;
 105:                     ldapCon.SessionOptions.RootDseCache = false;
 106:                     ldapCon.Bind();
 107:  
 108:                     return true;
 109:                 }
 110:             }
 111:             catch (Exception)
 112:             {
 113:                 return false;
 114:             }
 115:         }
 116:  
 117:         /// <summary>
 118:         /// This is event is used to handle certificate problems that occur during the binding process of connecting to an
 119:         /// LDAP server. The problem only seems to occur on Server 2008 machines
 120:         /// </summary>
 121:         /// <param name="connection"></param>
 122:         /// <param name="certificate"></param>
 123:         /// <returns></returns>
 124:         private static bool VerifyCertificateCallback(LdapConnection connection, X509Certificate certificate)
 125:         {
 126:             return true;
 127:         }
 128:  
 129:         /// <summary>
 130:         /// Fills a user object with user information from and LDAP response
 131:         /// </summary>
 132:         /// <param name="User_p"></param>
 133:         /// <param name="Response_p"></param>
 134:         private void FillUserObject(RouteYUser User_p, SearchResponse Response_p)
 135:         {
 136:             foreach (PropertyInfo property in User_p.GetType().GetProperties())
 137:             {
 138:                 try
 139:                 {
 140:                     RouteYLDAPField ldapField = property.GetCustomAttributes(true).OfType<RouteYLDAPField>().First();
 141:                     object userAttribute = Response_p.Entries[0].Attributes[ldapField.LDAPField][0];
 142:                     property.SetValue(User_p, Convert.ChangeType(userAttribute, property.PropertyType), null);
 143:                 }
 144:                 catch { }
 145:             }
 146:         }
 147:  
 148:         #endregion
 149:     }
 150: }

The next thing I have to do is setup the custom Login.ascx and Settings.ascx.

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Settings.ascx.cs" Inherits="DotNetNuke.Modules.Admin.Authentication.RouteYSettings" %>
   2: <%@ Register TagPrefix="dnn" TagName="Label" Src="~/controls/LabelControl.ascx" %>
   3: <table cellspacing="2" cellpadding="2" width="500" border="0" summary="Route Y Authentication Settings">
   4:     <tr>
   5:         <td class="SubHead" width="200">
   6:             <dnn:Label ID="lblEnabled" runat="server" ControlName="chkEnabled" Text="Enabled?" />
   7:         </td>
   8:         <td valign="top">
   9:             <asp:CheckBox ID="chkEnabled" runat="server" CssClass="NormalTextBox" />
  10:         </td>
  11:     </tr>
  12:     <tr>
  13:         <td class="SubHead" width="200">
  14:             <dnn:Label ID="lblAutoRegister" runat="server" ControlName="chkAutoRegister" Text="Auto Register:" />
  15:         </td>
  16:         <td valign="top">
  17:             <asp:CheckBox ID="chkAutoRegister" runat="server" CssClass="NormalTextBox" />
  18:         </td>
  19:     </tr>
  20:     <tr>
  21:         <td class="SubHead" width="200">
  22:             <dnn:Label ID="lblLDAPUrl" runat="server" ControlName="txtLDAPUrl" Text="LDAP Url:" />
  23:         </td>
  24:         <td valign="top">
  25:             <asp:TextBox ID="txtLDAPUrl" runat="server" Columns="40" CssClass="NormalTextBox"></asp:TextBox>
  26:         </td>
  27:     </tr>
  28:     <tr>
  29:         <td class="SubHead" width="200">
  30:             <dnn:Label ID="lblUserFormat" runat="server" ControlName="txtUserFormat" Text="LDAP User DN Format:" />
  31:         </td>
  32:         <td valign="top">
  33:             <asp:TextBox ID="txtUserFormat" runat="server" Columns="40" CssClass="NormalTextBox"></asp:TextBox>
  34:         </td>
  35:     </tr>
  36:     <tr>
  37:         <td class="SubHead" width="200">
  38:             <dnn:Label ID="lblStartingDn" runat="server" ControlName="txtStartingDn" Text="LDAP Starting DN:" />
  39:         </td>
  40:         <td valign="top">
  41:             <asp:TextBox ID="txtStartingDn" runat="server" Columns="40" CssClass="NormalTextBox"></asp:TextBox>
  42:         </td>
  43:     </tr>
  44:     <tr>
  45:         <td class="SubHead" width="200">
  46:             <dnn:Label ID="lblUsername" runat="server" ControlName="txtUsername" Text="LDAP Username:" />
  47:         </td>
  48:         <td valign="top">
  49:             <asp:TextBox ID="txtUsername" runat="server" Columns="40" CssClass="NormalTextBox"></asp:TextBox>
  50:         </td>
  51:     </tr>
  52:     <tr>
  53:         <td class="SubHead" width="200">
  54:             <dnn:Label ID="lblPassword" runat="server" ControlName="txtPassword" Text="LDAP Password:" />
  55:         </td>
  56:         <td valign="top">
  57:             <asp:TextBox ID="txtPassword" runat="server" Columns="40" CssClass="NormalTextBox"
  58:                 TextMode="Password"></asp:TextBox>
  59:         </td>
  60:     </tr>
  61: </table>
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5: using System.Web.UI;
   6: using System.Web.UI.WebControls;
   7: using DotNetNuke.Services.Authentication;
   8: using DotNetNuke.Services.Exceptions;
   9: using DotNetNuke.Authentication.RouteY;
  10:  
  11: namespace DotNetNuke.Modules.Admin.Authentication
  12: {
  13:     public partial class RouteYSettings : AuthenticationSettingsBase
  14:     {
  15:         protected void Page_Load(object sender, EventArgs e)
  16:         {
  17:             try
  18:             {
  19:                 // Load configuration settings
  20:                 if (!IsPostBack)
  21:                 {
  22:                     RouteYAuthConfig config = RouteYAuthConfig.GetConfig(PortalId);
  23:                     chkEnabled.Checked = config.Enabled;
  24:                     chkAutoRegister.Checked = config.AutoRegister;
  25:                     txtLDAPUrl.Text = config.LDAPUrl;
  26:                     txtUserFormat.Text = config.LDAPUserDNFormat;
  27:                     txtUsername.Text = config.LDAPUsername;
  28:                     txtStartingDn.Text = config.LDAPStartingDN;
  29:                 }
  30:             }
  31:             catch (Exception ex_p)
  32:             {
  33:                 Exceptions.ProcessModuleLoadException(this, ex_p);
  34:             }
  35:         }
  36:  
  37:         /// <summary>
  38:         /// This event fires when setting need to be updated.
  39:         /// </summary>
  40:         public override void UpdateSettings()
  41:         {
  42:             RouteYAuthConfig config = RouteYAuthConfig.GetConfig(PortalId);
  43:             config.Enabled = chkEnabled.Checked;
  44:             config.AutoRegister = chkAutoRegister.Checked;
  45:             config.LDAPUrl = txtLDAPUrl.Text;
  46:             config.LDAPUserDNFormat = txtUserFormat.Text;
  47:             config.LDAPUsername = txtUsername.Text;
  48:             if (!String.IsNullOrEmpty(txtPassword.Text))
  49:                 config.LDAPPassword = txtPassword.Text;
  50:             config.LDAPStartingDN = txtStartingDn.Text;
  51:             RouteYAuthConfig.UpdateConfig(config);
  52:         }
  53:     }
  54: }
   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="Login.ascx.cs" Inherits="DotNetNuke.Modules.Admin.Authentication.RouteYLogin" %>
   2: <asp:Panel ID="panelLogin" runat="server" DefaultButton="lgRouteYLogin$LoginButton">
   3:     <asp:Login ID="lgRouteYLogin" runat="server" BackColor="#F7F6F3" BorderColor="#E6E2D8"
   4:         BorderPadding="4" BorderStyle="Solid" BorderWidth="1px" Font-Names="Verdana"
   5:         Font-Size="1em" ForeColor="#333333" DisplayRememberMe="False" TextLayout="TextOnTop"
   6:         Width="200px" TitleText="" UserNameLabelText="Net ID:" UserNameRequiredErrorMessage="Net ID is required."
   7:         OnAuthenticate="lgRouteYLogin_Authenticate" FailureText="">
   8:         <TextBoxStyle Font-Size="1em" Width="180px" />
   9:         <LoginButtonStyle BackColor="#FFFBFF" BorderColor="#CCCCCC" BorderStyle="Solid" BorderWidth="1px"
  10:             Font-Names="Verdana" Font-Size="1em" ForeColor="#284775" />
  11:         <InstructionTextStyle Font-Italic="True" ForeColor="Black" />
  12:         <TitleTextStyle BackColor="#5D7B9D" Font-Bold="True" Font-Size="1em" ForeColor="White" />
  13:     </asp:Login>
  14: </asp:Panel>
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Reflection;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using DotNetNuke.Authentication.RouteY;
  10: using DotNetNuke.Common;
  11: using DotNetNuke.Common.Lists;
  12: using DotNetNuke.Common.Utilities;
  13: using DotNetNuke.Entities.Portals;
  14: using DotNetNuke.Entities.Profile;
  15: using DotNetNuke.Entities.Users;
  16: using DotNetNuke.Security.Membership;
  17: using DotNetNuke.Services.Authentication;
  18: using DotNetNuke.Services.Localization;
  19: using DotNetNuke.Entities.Modules;
  20: using DotNetNuke.Services.Log.EventLog;
  21: using DotNetNuke.Security.Roles;
  22: using System.Collections;
  23:  
  24: namespace DotNetNuke.Modules.Admin.Authentication
  25: {
  26:     public partial class RouteYLogin : AuthenticationLoginBase
  27:     {
  28:         #region Properties
  29:  
  30:         /// <summary>
  31:         /// Overridden from parent class. Gets the config setting informing the user login module
  32:         /// weather or not this authentication provider is enabled or not.
  33:         /// </summary>
  34:         public override bool Enabled
  35:         {
  36:             get { return RouteYAuthConfig.GetConfig(PortalId).Enabled; }
  37:         }
  38:  
  39:         /// <summary>
  40:         /// Gets the value indicating if a valid route Y user should be automatically registered.
  41:         /// </summary>
  42:         public bool AutoRegister
  43:         {
  44:             get { return RouteYAuthConfig.GetConfig(PortalId).AutoRegister; }
  45:         }
  46:  
  47:         /// <summary>
  48:         /// Gets a list of valid data types used to define user profile properties
  49:         /// </summary>
  50:         private List<ListEntryInfo> ProfileDataTypes
  51:         {
  52:             get
  53:             {
  54:                 ListController lists = new ListController();
  55:                 ListEntryInfoCollection dataTypeList = lists.GetListEntryInfoCollection("DataType");
  56:                 List<ListEntryInfo> dataTypes = new List<ListEntryInfo>();
  57:  
  58:                 foreach (ListEntryInfo listEntryInfo in dataTypeList)
  59:                 {
  60:                     dataTypes.Add(listEntryInfo);
  61:                 }
  62:  
  63:                 return dataTypes;
  64:             }
  65:         }
  66:  
  67:         #endregion
  68:  
  69:         /// <summary>
  70:         /// Checks to make sure that the connection is secure. We don't want to allow the uses of this form of authentication
  71:         /// if the connection is not secure.
  72:         /// </summary>
  73:         /// <param name="sender"></param>
  74:         /// <param name="e"></param>
  75:         protected void Page_Load(object sender, EventArgs e)
  76:         {
  77:             // Makes sure that our connection is secure
  78:             if (!Request.IsSecureConnection)
  79:             {
  80:                 lgRouteYLogin.Visible = false;
  81:                 Controls.Add(new Literal() { Text = Localization.GetString("[No Ssl Error]", LocalResourceFile) });
  82:                 return;
  83:             }
  84:         }
  85:  
  86:         /// <summary>
  87:         /// This event fires when a user attempts to login with the this authentication provider. Authentication occurs in a couple steps:
  88:         /// 1. Valid username/password with Route Y LDAP
  89:         /// 2. Check DNN user store to make sure a user with the given Net ID actually exists in the portal
  90:         /// 3. Ensure that the user has an association made in the system with this form of authentication, if not exists, make one
  91:         /// 4. Sync profile with Route Y info
  92:         /// </summary>
  93:         /// <param name="sender"></param>
  94:         /// <param name="e"></param>
  95:         protected void lgRouteYLogin_Authenticate(object sender, AuthenticateEventArgs e)
  96:         {
  97:             RouteYAuthentication routeYAuth = new RouteYAuthentication(RouteYAuthConfig.GetConfig(PortalId));
  98:             UserLoginStatus loginStatus = UserLoginStatus.LOGIN_FAILURE;
  99:             DotNetNuke.Entities.Users.UserInfo userObj = null;
 100:             RouteYUser routeYUser = null;
 101:  
 102:             if (!routeYAuth.AuthenticateUser(lgRouteYLogin.UserName, lgRouteYLogin.Password))
 103:             {
 104:                 e.Authenticated = false;
 105:             }
 106:             else
 107:             {
 108:                 try
 109:                 {
 110:                     routeYUser = routeYAuth.FindUser(lgRouteYLogin.UserName);
 111:                 }
 112:                 catch (Exception ex_p)
 113:                 {
 114:                     throw new Exception(String.Format("The Route Y user lookup failed with the following error: {0} - Please check your Route Y authentication provider settings.", ex_p.Message));
 115:                 }
 116:  
 117:                 userObj = UserController.GetUserByName(PortalId, lgRouteYLogin.UserName);
 118:  
 119:                 // Handle auto registration
 120:                 if (userObj == null && AutoRegister == true)
 121:                 {
 122:                     // Check if user already exists in some form in other portals
 123:                     userObj = UserController.GetUserByName(Null.NullInteger, lgRouteYLogin.UserName);
 124:                     UserCreateStatus createStatus;
 125:                     if (userObj == null) // User doesn't exist in any portal
 126:                     {
 127:                         userObj = new UserInfo()
 128:                         {
 129:                             DisplayName = routeYUser.FullName,
 130:                             Email = routeYUser.Email,
 131:                             FirstName = routeYUser.FirstName,
 132:                             IsDeleted = false,
 133:                             IsSuperUser = false,
 134:                             LastName = routeYUser.LastName,
 135:                             PortalID = PortalId,
 136:                             Username = lgRouteYLogin.UserName
 137:                         };
 138:                         userObj.Membership.Password = UserController.GeneratePassword();
 139:                         createStatus = UserController.CreateUser(ref userObj);
 140:                     }
 141:                     else
 142:                     {
 143:                         int userID = DotNetNuke.Security.Membership.Data.DataProvider.Instance().AddUser(PortalId,
 144:                             userObj.Username, userObj.FirstName, userObj.LastName, userObj.AffiliateID,
 145:                             userObj.IsSuperUser, userObj.Email, userObj.DisplayName, true, true, userObj.CreatedByUserID);
 146:                         userObj = UserController.GetUserById(PortalId, userID);
 147:                         createStatus = UserCreateStatus.Success;
 148:                         RoleController roleCtl = new RoleController();
 149:                         IEnumerable<RoleInfo> roles = roleCtl.GetPortalRoles(PortalId).OfType<RoleInfo>();
 150:                         foreach (RoleInfo role in roles.Where(item => item.AutoAssignment == true))
 151:                         {
 152:                             roleCtl.AddUserRole(PortalId, userID, role.RoleID, Null.NullDate);
 153:                         }
 154:                     }
 155:                 }
 156:  
 157:                 // Check to make sure the user is associated with the portal and is a validate user
 158:                 if (userObj != null && !userObj.IsDeleted)
 159:                 {
 160:                     // We now need to check if this user has been associated with this form of authentication
 161:                     DotNetNuke.Entities.Users.UserInfo userAuthObj = UserController.ValidateUser(PortalId, lgRouteYLogin.UserName, String.Empty, "RouteY", String.Empty, PortalSettings.PortalName, IPAddress, ref loginStatus);
 162:                     if (userAuthObj == null) // User has not been assoicated, so associate and re-validate
 163:                     {
 164:                         try
 165:                         {
 166:                             AuthenticationController.AddUserAuthentication(userObj.UserID, "RouteY", userObj.Username);
 167:                         }
 168:                         catch
 169:                         {
 170:                             // Already added
 171:                         }
 172:                         userObj = UserController.ValidateUser(PortalId, lgRouteYLogin.UserName, String.Empty, "RouteY", String.Empty, PortalSettings.PortalName, IPAddress, ref loginStatus);
 173:  
 174:                     }
 175:                     else // User is associated
 176:                     {
 177:                         userObj = userAuthObj;
 178:                     }
 179:                     SyncProfile(routeYUser, userObj);
 180:                     e.Authenticated = true;
 181:                 }
 182:                 else
 183:                 {
 184:                     e.Authenticated = false;
 185:                     userObj = null;
 186:                 }
 187:             }
 188:  
 189:             UserAuthenticatedEventArgs eventArgs = new UserAuthenticatedEventArgs(userObj, lgRouteYLogin.UserName, loginStatus, "RouteY");
 190:             eventArgs.Authenticated = loginStatus != UserLoginStatus.LOGIN_FAILURE;
 191:             if (e.Authenticated)
 192:                 eventArgs.Profile = routeYUser.GetProfile();
 193:             this.OnUserAuthenticated(eventArgs);
 194:         }
 195:  
 196:         /// <summary>
 197:         /// Syncs a route y profile to a valid DNN user object
 198:         /// </summary>
 199:         /// <param name="RouteYUser_p"></param>
 200:         /// <param name="DNNUserObj_p"></param>
 201:         private void SyncProfile(RouteYUser RouteYUser_p, UserInfo DNNUserObj_p)
 202:         {
 203:             try
 204:             {
 205:                 if (RouteYUser_p == null)
 206:                     throw new Exception("RouteY User object is null");
 207:                 if (DNNUserObj_p == null)
 208:                     throw new Exception("DNN User object is null");
 209:  
 210:                 // Make sure that all Route Y profile properties are also properties of a DNN user profile
 211:                 ProfilePropertyDefinitionCollection profileProperties = ProfileController.GetPropertyDefinitionsByPortal(PortalId);
 212:                 ProfilePropertyDefinitionCollection hostProfileProperties = ProfileController.GetPropertyDefinitionsByPortal(-1); // Host profile definitions
 213:                 profileProperties.Sort();
 214:                 int lastViewOrder = profileProperties[profileProperties.Count - 1].ViewOrder;
 215:                 foreach (PropertyInfo propertyInfo in RouteYUser_p.GetType().GetProperties())
 216:                 {
 217:                     // Check portal profile definition
 218:                     ProfilePropertyDefinition ppDefinition = profileProperties.GetByName(propertyInfo.Name);
 219:                     if (ppDefinition == null)
 220:                     {
 221:                         ProfileController.AddPropertyDefinition(new ProfilePropertyDefinition(PortalId)
 222:                         {
 223:                             DataType = ProfileDataTypes.Where(type => type.Value == "Text").First().EntryID,
 224:                             Length = 0,
 225:                             PropertyCategory = "RouteY",
 226:                             PropertyName = propertyInfo.Name,
 227:                             Required = false,
 228:                             ViewOrder = ++lastViewOrder,
 229:                             Visibility = UserVisibilityMode.AdminOnly,
 230:                             Visible = true,
 231:                         });
 232:                     }
 233:  
 234:                     // Check host profile definition
 235:                     ppDefinition = hostProfileProperties.GetByName(propertyInfo.Name);
 236:                     if (ppDefinition == null)
 237:                     {
 238:                         ProfileController.AddPropertyDefinition(new ProfilePropertyDefinition(-1)
 239:                         {
 240:                             DataType = ProfileDataTypes.Where(type => type.Value == "Text").First().EntryID,
 241:                             Length = 0,
 242:                             PropertyCategory = "RouteY",
 243:                             PropertyName = propertyInfo.Name,
 244:                             Required = false,
 245:                             ViewOrder = ++lastViewOrder,
 246:                             Visibility = UserVisibilityMode.AdminOnly,
 247:                             Visible = true,
 248:                         });
 249:                     }
 250:  
 251:                     // Sync actual profile property
 252:                     if (propertyInfo.GetValue(RouteYUser_p, null) != null)
 253:                         DNNUserObj_p.Profile.SetProfileProperty(propertyInfo.Name, propertyInfo.GetValue(RouteYUser_p, null).ToString());
 254:                 }
 255:             }
 256:             catch (Exception ex_p)
 257:             {
 258:                 try
 259:                 {
 260:                     ExceptionLogController log = new ExceptionLogController();
 261:                     log.AddLog(ex_p);
 262:                     //EventLogController log = new EventLogController();
 263:                     ////LogController log = new LogController();
 264:                     //LogInfo logInfo = new LogInfo(String.Format("An error occured during profile syncing: {0}", ex_p.Message));
 265:                     //logInfo.LogPortalID = PortalId;
 266:                     //logInfo.LogPortalName = PortalSettings.PortalName;
 267:                     //log.AddLog(logInfo);
 268:                 }
 269:                 catch { }
 270:             }
 271:         }
 272:     }
 273: }

I’d like to point out two area of code that caused me some consternation. One, is line 143 of the RouteYLogin class. Normally you are supposed to go through the DNN API to add an already existing user in another portal to a different portal. However, you need to know the user’s password in order to do that. To get around this issue, I just call down into the data provider method that adds users to the system. This particular call either adds a user if they don’t exist anywhere in the DNN database or it will just associate an already existing user to the portal so that they can now log in. Second, line 166—this took some time to find. Apparently, if you are going to use alternative means of authentication, enable that form of authentication for every user. What that means is that when a user logins in, however they login must eventually map back to DNN’s store of users. There were no examples I could find on this so it took a while to find the API call. That’s generally it. The rest of the code syncs a user’s profile from the LDAP to DNN so that it is available for html replacement and other modules that make good use for profile data. I hope this helps.

No comments:

Post a Comment