Browse files

Add authorization and gateway to LDAP plugin.

  - Group authorization
  - Adding to local groups based on LDAP group membership
  - Closes #116
  • Loading branch information...
1 parent d082227 commit f21da5140319614d6a0f812315a4ee7dde4f0158 @daw42 daw42 committed May 6, 2012
View
9 Plugins/LdapPlugin/Ldap/Configuration.cs
@@ -292,15 +292,6 @@ private bool ValidateInput()
return false;
}
- if ( (searchForDnCheckBox.Checked ||
- authzRulesListBox.Items.Count > 0 ||
- gatewayRulesListBox.Items.Count > 0) &&
- string.IsNullOrEmpty(searchDnTextBox.Text.Trim()) )
- {
- MessageBox.Show("WARNING: You should provide a Search DN when \n" +
- " \"Search for DN\" is enabled or using a group authorization \n" +
- " or gateway rule.");
- }
if ((authzRulesListBox.Items.Count > 0 ||
gatewayRulesListBox.Items.Count > 0) && (
string.IsNullOrEmpty(this.groupDNPattern.Text.Trim()) ||
View
179 Plugins/LdapPlugin/Ldap/LdapAuthenticator.cs
@@ -46,10 +46,12 @@ class LdapAuthenticator
{
private ILog m_logger = LogManager.GetLogger("LdapAuthenticator");
private NetworkCredential m_creds;
+ private LdapServer m_serv;
- public LdapAuthenticator(NetworkCredential creds)
+ public LdapAuthenticator(NetworkCredential creds, LdapServer serv)
{
m_creds = creds;
+ m_serv = serv;
}
public BooleanResult Authenticate()
@@ -66,133 +68,91 @@ public BooleanResult Authenticate()
// Generate username (if we're not doing a search for it)
string userDN = null;
bool doSearch = Settings.Store.DoSearch;
- if ( ! doSearch )
+ if (!doSearch)
{
userDN = CreateUserDN();
}
-
- X509Certificate2 serverCert = null;
- bool useSsl = Settings.Store.UseSsl;
- bool requireCert = Settings.Store.RequireCert;
- string certFile = Settings.Store.ServerCertFile;
- if( useSsl && requireCert && certFile.Length > 0 )
- {
- if (File.Exists(certFile))
- {
- m_logger.DebugFormat("Loading server certificate: {0}", certFile);
- serverCert = new X509Certificate2(certFile);
- }
- else
- {
- m_logger.ErrorFormat("Certificate file {0} not found, giving up.", certFile);
- return new BooleanResult{ Success = false, Message = "Server certificate not found" };
- }
- }
-
- string[] hosts = Settings.Store.LdapHost;
- int port = Settings.Store.LdapPort;
- using (LdapServer serv = new LdapServer(hosts, port, useSsl, requireCert, serverCert))
+
+ try
{
- try
+ // If we're searching, attempt to bind with the search credentials, or anonymously
+ if (doSearch)
{
- // Connect. Note that this always succeeds whether or not the server is
- // actually available. It not clear to me whether this actually talks to the server at all.
- // The timeout only seems to take effect when binding.
- int timeout = Settings.Store.LdapTimeout;
- serv.Connect(timeout);
-
- // If we're searching, attempt to bind with the search credentials, or anonymously
- if (doSearch)
+ // Set this to null (should be null anyway) because we are going to search
+ // for it.
+ userDN = null;
+ try
{
- // Set this to null (should be null anyway) because we are going to search
- // for it.
- userDN = null;
- try
- {
- // Attempt to bind in order to do the search
- string searchDN = Settings.Store.SearchDN;
- string searchPW = Settings.Store.GetEncryptedSetting("SearchPW");
- if (searchDN.Length > 0)
- {
- NetworkCredential creds = new NetworkCredential(searchDN, searchPW);
- m_logger.DebugFormat("Attempting to bind with DN: {0} for search", creds.UserName);
- serv.Bind(creds);
- }
- else
- {
- m_logger.DebugFormat("Attempting to bind anonymously for search.");
- serv.Bind();
- }
+ // Attempt to bind in order to do the search
+ m_serv.BindForSearch();
- // If we get here, a bind was successful, so we can search for the user's DN
- userDN = FindUserDN(serv);
- }
- catch (LdapException e)
- {
- if (e.ErrorCode == 81)
- {
- m_logger.ErrorFormat("Server unavailable: {0}", e.Message);
- }
- else if (e.ErrorCode == 49)
- {
- m_logger.ErrorFormat("Bind failed: invalid credentials.");
- }
- else
- {
- m_logger.ErrorFormat("Exception ({0}) when binding for search: {1}", e.ErrorCode, e);
- }
-
- return new BooleanResult { Success = false, Message = "Unable to contact LDAP server." };
- }
+ // If we get here, a bind was successful, so we can search for the user's DN
+ userDN = FindUserDN();
}
-
- // If we've got a userDN, attempt to authenticate the user
- if (userDN != null)
+ catch (LdapException e)
{
- try
+ if (e.ErrorCode == 81)
{
- // Attempt to bind with the user's LDAP credentials
- m_logger.DebugFormat("Attempting to bind with DN {0}", userDN);
- NetworkCredential ldapCredential = new NetworkCredential(userDN, m_creds.Password);
- serv.Bind(ldapCredential);
-
- // If we get here, the authentication was successful, we're done!
- m_logger.DebugFormat("LDAP DN {0} successfully bound to server, return success", ldapCredential.UserName);
- return new BooleanResult { Success = true };
+ m_logger.ErrorFormat("Server unavailable: {0}", e.Message);
}
- catch (LdapException e)
+ else if (e.ErrorCode == 49)
{
- if (e.ErrorCode == 81)
- {
- m_logger.ErrorFormat("Server unavailable: " + e.Message);
- return new BooleanResult { Success = false, Message = "Failed to contact LDAP server." };
- }
- else if (e.ErrorCode == 49)
- {
- m_logger.ErrorFormat("Bind failed for LDAP DN {0}: invalid credentials.", userDN);
- return new BooleanResult { Success = false, Message = "Authentication via LDAP failed. Invalid credentials." };
- }
- else
- {
- m_logger.ErrorFormat("Exception ({0}) when binding for authentication: {1}", e.ErrorCode, e.Message);
- return new BooleanResult { Success = false, Message = "Authentication via LDAP failed: " + e.Message };
- }
+ m_logger.ErrorFormat("Bind failed: invalid credentials.");
+ }
+ else
+ {
+ m_logger.ErrorFormat("Exception ({0}) when binding for search: {1}", e.ErrorCode, e);
}
- } // end if(userDN != null)
+ return new BooleanResult { Success = false, Message = "Unable to contact LDAP server." };
+ }
}
- catch (Exception e)
+
+ // If we've got a userDN, attempt to authenticate the user
+ if (userDN != null)
{
- if (e is LdapException)
+ try
{
- m_logger.ErrorFormat("LdapException ({0}): {1}", ((LdapException)e).ErrorCode, e);
+ // Attempt to bind with the user's LDAP credentials
+ m_logger.DebugFormat("Attempting to bind with DN {0}", userDN);
+ NetworkCredential ldapCredential = new NetworkCredential(userDN, m_creds.Password);
+ m_serv.Bind(ldapCredential);
+
+ // If we get here, the authentication was successful, we're done!
+ m_logger.DebugFormat("LDAP DN {0} successfully bound to server, return success", ldapCredential.UserName);
+ return new BooleanResult { Success = true };
}
- else
+ catch (LdapException e)
{
- m_logger.DebugFormat("Exception: {0}", e);
+ if (e.ErrorCode == 81)
+ {
+ m_logger.ErrorFormat("Server unavailable: " + e.Message);
+ return new BooleanResult { Success = false, Message = "Failed to contact LDAP server." };
+ }
+ else if (e.ErrorCode == 49)
+ {
+ m_logger.ErrorFormat("Bind failed for LDAP DN {0}: invalid credentials.", userDN);
+ return new BooleanResult { Success = false, Message = "Authentication via LDAP failed. Invalid credentials." };
+ }
+ else
+ {
+ m_logger.ErrorFormat("Exception ({0}) when binding for authentication: {1}", e.ErrorCode, e.Message);
+ return new BooleanResult { Success = false, Message = "Authentication via LDAP failed: " + e.Message };
+ }
}
+ } // end if(userDN != null)
+ }
+ catch (Exception e)
+ {
+ if (e is LdapException)
+ {
+ m_logger.ErrorFormat("LdapException ({0}): {1}", ((LdapException)e).ErrorCode, e);
}
- } // end using
+ else
+ {
+ m_logger.DebugFormat("Exception: {0}", e);
+ }
+ }
return new BooleanResult{ Success = false, Message = "Authentication via LDAP failed." };
}
@@ -203,9 +163,8 @@ public BooleanResult Authenticate()
/// The search filter is taken from Settings.Store.SearchFilter. If all
/// searches fail, this method returns null.
/// </summary>
- /// <param name="serv">The LdapServer to use when performing the search.</param>
/// <returns>The DN of the first object found, or null if searches fail.</returns>
- private string FindUserDN(LdapServer serv)
+ private string FindUserDN()
{
string filter = CreateSearchFilter();
@@ -217,7 +176,7 @@ private string FindUserDN(LdapServer serv)
string dn = null;
try
{
- dn = serv.FindFirstDN(context, filter);
+ dn = m_serv.FindFirstDN(context, filter);
}
catch (DirectoryOperationException e)
{
View
88 Plugins/LdapPlugin/Ldap/LdapServer.cs
@@ -34,6 +34,8 @@
using System.Net;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
+using System.IO;
+using System.Text.RegularExpressions;
namespace pGina.Plugin.Ldap
{
@@ -71,27 +73,39 @@ public class LdapServer : IDisposable
/// </summary>
public int Timeout { get; set; }
- /// <summary>
- /// Create a connection to the LDAP server at the given host and port.
- /// </summary>
- /// <param name="host">The FQDN or IP of the LDAP host.</param>
- /// <param name="port">The port number of the LDAP host.</param>
- /// <param name="useSsl">Whether or not to use SSL.</param>
- /// <param name="verifyCert">Whether or not to verify the server certificate.</param>
- /// <param name="cert">A certificate to verify against the server's certificate (can be null). If this is null,
- /// and verifyCert is true, then the server's cert is verified with the Windows certificate store.</param>
- public LdapServer(string[] hosts, int port, bool useSsl, bool verifyCert, X509Certificate2 cert)
+ public LdapServer()
{
- m_logger.DebugFormat("Initializing LdapServer host(s): [{0}], port: {1}, useSSL = {2}, verifyCert = {3}",
- string.Join(", ", hosts), port, useSsl, verifyCert);
m_conn = null;
+ m_cert = null;
+ Timeout = Settings.Store.LdapTimeout;
+ m_useSsl = Settings.Store.UseSsl;
+ m_verifyCert = Settings.Store.RequireCert;
+ string certFile = Settings.Store.ServerCertFile;
+ if (m_useSsl && m_verifyCert)
+ {
+ if (File.Exists(certFile))
+ {
+ m_logger.DebugFormat("Loading server certificate: {0}", certFile);
+ m_cert = new X509Certificate2(certFile);
+ }
+ else
+ {
+ m_logger.ErrorFormat("Certificate file {0} not found.", certFile);
+ throw new Exception("Server certificate not found");
+ }
+ }
+
+ string[] hosts = Settings.Store.LdapHost;
+ int port = Settings.Store.LdapPort;
m_serverIdentifier = new LdapDirectoryIdentifier(hosts, port, false, false);
- m_useSsl = useSsl;
- m_verifyCert = verifyCert;
- m_cert = cert;
+
+ m_logger.DebugFormat("Initializing LdapServer host(s): [{0}], port: {1}, useSSL = {2}, verifyCert = {3}",
+ string.Join(", ", hosts), port, m_useSsl, m_verifyCert);
+
+ this.Connect();
}
- public void Connect(int timeout)
+ private void Connect()
{
// Are we re-connecting? If so, close the previous connection.
if (m_conn != null)
@@ -100,8 +114,8 @@ public void Connect(int timeout)
}
m_conn = new LdapConnection(m_serverIdentifier);
- m_conn.Timeout = new System.TimeSpan(0,0,timeout);
- m_logger.DebugFormat("Timeout set to {0} seconds.", timeout);
+ m_conn.Timeout = new System.TimeSpan(0,0,Timeout);
+ m_logger.DebugFormat("Timeout set to {0} seconds.", Timeout);
m_conn.SessionOptions.ProtocolVersion = 3;
m_conn.SessionOptions.SecureSocketLayer = m_useSsl;
if( m_useSsl )
@@ -196,6 +210,17 @@ public void Bind()
}
}
+ public void BindForSearch()
+ {
+ string searchDn = Settings.Store.SearchDN;
+ string searchPw = Settings.Store.GetEncryptedSetting("SearchPW");
+
+ if (string.IsNullOrEmpty(searchDn))
+ this.Bind();
+ else
+ this.Bind(new NetworkCredential(searchDn, searchPw));
+ }
+
/// <summary>
/// Try to bind to the LDAP server with the given credentials. This uses
/// basic authentication. Throws LdapException if the bind fails.
@@ -258,6 +283,33 @@ public string FindFirstDN(string searchBase, string filter)
return null;
}
+ public bool MemberOfGroup(string user, string group)
+ {
+ string groupDn = Settings.Store.GroupDnPattern;
+ string groupAttribute = Settings.Store.GroupMemberAttrib;
+
+ if (string.IsNullOrEmpty(groupDn))
+ throw new Exception("Can't resolve group DN, group DN pattern missing.");
+
+ if (string.IsNullOrEmpty(groupAttribute))
+ throw new Exception("Can't resolve group membership, group attribute missing.");
+
+ groupDn = Regex.Replace(groupDn, @"\%g", group);
+ string filter = string.Format("({0}={1})", groupAttribute, user);
+ m_logger.DebugFormat("Searching for group membership, DN: {0} Filter: {1}", groupDn, filter);
+ try
+ {
+ SearchRequest req = new SearchRequest(groupDn, filter, SearchScope.Base, null);
+ SearchResponse resp = (SearchResponse)m_conn.SendRequest(req);
+ return resp.Entries.Count > 0;
+ }
+ catch (DirectoryOperationException e)
+ {
+ m_logger.ErrorFormat("Error when checking for group membership: {0}", e.Message);
+ return false;
+ }
+ }
+
public void Dispose()
{
this.Close();
View
159 Plugins/LdapPlugin/Ldap/Plugin.cs
@@ -39,7 +39,7 @@
namespace pGina.Plugin.Ldap
{
- public class LdapPlugin : IPluginAuthentication, IPluginConfiguration
+ public class LdapPlugin : IStatefulPlugin, IPluginAuthentication, IPluginAuthorization, IPluginAuthenticationGateway, IPluginConfiguration
{
public static readonly Guid LdapUuid = new Guid("{0F52390B-C781-43AE-BD62-553C77FA4CF7}");
private ILog m_logger = LogManager.GetLogger("LdapPlugin");
@@ -80,17 +80,19 @@ public BooleanResult AuthenticateUser(Shared.Types.SessionProperties properties)
try
{
m_logger.DebugFormat("AuthenticateUser({0})", properties.Id.ToString());
-
Shared.Types.UserInformation userInfo = properties.GetTrackedSingle<Shared.Types.UserInformation>();
-
m_logger.DebugFormat("Received username: {0}", userInfo.Username);
// Place credentials into a NetworkCredentials object
NetworkCredential creds = new NetworkCredential(userInfo.Username, userInfo.Password);
// Authenticate the login
+ LdapServer server = properties.GetTrackedSingle<LdapServer>();
+ if (server == null)
+ return new BooleanResult() { Success = false, Message = "Internal error: LdapServer object not available" };
+
m_logger.DebugFormat("Attempting authentication for {0}", creds.UserName);
- LdapAuthenticator authenticator = new LdapAuthenticator(creds);
+ LdapAuthenticator authenticator = new LdapAuthenticator(creds, server);
return authenticator.Authenticate();
}
catch (Exception e)
@@ -108,5 +110,154 @@ public void Configure()
public void Starting() { }
public void Stopping() { }
+
+ public void BeginChain(SessionProperties props)
+ {
+ m_logger.Debug("BeginChain");
+ try
+ {
+ LdapServer serv = new LdapServer();
+ props.AddTrackedSingle<LdapServer>(serv);
+ }
+ catch (Exception e)
+ {
+ m_logger.ErrorFormat("Failed to create LdapServer: {0}", e.Message);
+ props.AddTrackedSingle<LdapServer>(null);
+ }
+ }
+
+ public void EndChain(SessionProperties props)
+ {
+ m_logger.Debug("EndChain");
+ LdapServer serv = props.GetTrackedSingle<LdapServer>();
+ if (serv != null) serv.Close();
+ }
+
+ public BooleanResult AuthorizeUser(SessionProperties properties)
+ {
+ m_logger.Debug("LDAP Plugin Authorization");
+ try
+ {
+ UserInformation userInfo = properties.GetTrackedSingle<UserInformation>();
+ string user = userInfo.Username;
+ LdapServer serv = properties.GetTrackedSingle<LdapServer>();
+ if (serv == null)
+ {
+ m_logger.ErrorFormat("AuthorizeUser: Internal error, LdapServer object not available.");
+ return new BooleanResult() { Success = false, Message = "LDAP server not available" };
+ }
+
+ List<GroupAuthzRule> rules = GroupRuleLoader.GetAuthzRules();
+
+ // Bind for searching if we have rules to process. If there's only one, it's the
+ // default rule which doesn't require searching the LDAP tree.
+ if (rules.Count > 1)
+ serv.BindForSearch();
+
+ foreach (GroupAuthzRule rule in rules)
+ {
+ bool inGroup = false;
+
+ // Don't need to check membership if the condition is "always." This is the
+ // case for the default rule only. which is the last rule in the list.
+ if (rule.RuleCondition != GroupRule.Condition.ALWAYS)
+ {
+ inGroup = serv.MemberOfGroup(user, rule.Group);
+ m_logger.DebugFormat("User {0} {1} member of group {2}", user, inGroup ? "is" : "is not",
+ rule.Group);
+ }
+
+ if (rule.RuleMatch(inGroup))
+ {
+ if (rule.AllowOnMatch)
+ return new BooleanResult()
+ {
+ Success = true,
+ Message = string.Format("Allow via rule: \"{0}\"", rule.ToString())
+ };
+ else
+ return new BooleanResult()
+ {
+ Success = false,
+ Message = string.Format("Deny via rule: \"{0}\"", rule.ToString())
+ };
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ m_logger.ErrorFormat("Error during authorization: {0}", e);
+
+ // Error causes failure to authorize
+ return new BooleanResult() { Success = false, Message = e.Message };
+ }
+
+ // We should never get this far because the last rule in the list should always be a match,
+ // but if for some reason we do, return success.
+ return new BooleanResult() { Success = true, Message = "" };
+ }
+
+ public BooleanResult AuthenticatedUserGateway(SessionProperties properties)
+ {
+ m_logger.Debug("LDAP Plugin Gateway");
+ List<string> addedGroups = new List<string>();
+
+ try
+ {
+ UserInformation userInfo = properties.GetTrackedSingle<UserInformation>();
+ string user = userInfo.Username;
+ LdapServer serv = properties.GetTrackedSingle<LdapServer>();
+ if (serv == null)
+ {
+ m_logger.ErrorFormat("AuthenticatedUserGateway: Internal error, LdapServer object not available.");
+ return new BooleanResult() { Success = false, Message = "LDAP server not available" };
+ }
+
+ List<GroupGatewayRule> rules = GroupRuleLoader.GetGatewayRules();
+ bool boundToServ = false;
+ foreach (GroupGatewayRule rule in rules)
+ {
+ bool inGroup = false;
+
+ // Don't need to check for group membership if the rule is to be always applied.
+ if (rule.RuleCondition != GroupRule.Condition.ALWAYS)
+ {
+ // If we haven't bound to server yet, do so.
+ if (!boundToServ)
+ {
+ serv.BindForSearch();
+ boundToServ = true;
+ }
+
+ inGroup = serv.MemberOfGroup(user, rule.Group);
+ m_logger.DebugFormat("User {0} {1} member of group {2}", user, inGroup ? "is" : "is not",
+ rule.Group);
+ }
+
+ if (rule.RuleMatch(inGroup))
+ {
+ m_logger.InfoFormat("Adding user {0} to local group {1}, due to rule \"{2}\"",
+ user, rule.LocalGroup, rule.ToString());
+ addedGroups.Add(rule.LocalGroup);
+ userInfo.AddGroup( new GroupInformation() { Name = rule.LocalGroup } );
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ m_logger.ErrorFormat("Error during gateway: {0}", e);
+
+ // Error does not cause failure
+ return new BooleanResult() { Success = true, Message = e.Message };
+ }
+
+ string message = "";
+ if (addedGroups.Count > 0)
+ message = string.Format("Added to groups: {0}", string.Join(", ", addedGroups));
+ else
+ message = "No groups added.";
+
+ return new BooleanResult() { Success = true, Message = message };
+ }
}
}
View
1 Plugins/LdapPlugin/Tests/Tests.csproj
@@ -54,7 +54,6 @@
</Reference>
</ItemGroup>
<ItemGroup>
- <Compile Include="TestLdapServer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunConfig.cs" />
</ItemGroup>

0 comments on commit f21da51

Please sign in to comment.