summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <simo@redhat.com>2014-08-28 14:59:13 -0400
committerPatrick Uiterwijk <puiterwijk@redhat.com>2014-09-24 20:51:53 +0200
commitf8699581dbcf5ba39a93b6202e577260c498b102 (patch)
tree8acbbb7165a2e35c790ae3cde196fd9fd4906352
parent39375ee6ce6b65737670f5a7f850d6f75d328d21 (diff)
downloadipsilon-f8699581dbcf5ba39a93b6202e577260c498b102.tar.gz
ipsilon-f8699581dbcf5ba39a93b6202e577260c498b102.tar.xz
ipsilon-f8699581dbcf5ba39a93b6202e577260c498b102.zip
Add very simple LDAP authentication plugin
Uses python-ldap to perform a simple bind after connecting to the LDAP server using (by default) a TLS encrypted connection. Signed-off-by: Simo Sorce <simo@redhat.com> Reviewed-by: Patrick Uiterwijk <puiterwijk@redhat.com>
-rwxr-xr-xipsilon/info/infoldap.py172
-rwxr-xr-xipsilon/login/authldap.py221
2 files changed, 393 insertions, 0 deletions
diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py
new file mode 100755
index 0000000..6d710bd
--- /dev/null
+++ b/ipsilon/info/infoldap.py
@@ -0,0 +1,172 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Ipsilon Project Contributors
+#
+# See the file named COPYING for the project license
+
+from ipsilon.info.common import InfoProviderBase
+from ipsilon.info.common import InfoProviderInstaller
+from ipsilon.util.plugin import PluginObject
+from ipsilon.util.log import Log
+import ldap
+
+
+class InfoProvider(InfoProviderBase, Log):
+
+ def __init__(self):
+ super(InfoProvider, self).__init__()
+ self.name = 'ldap'
+ self.description = """
+Info plugin that uses LDAP to retrieve user data. """
+ self._options = {
+ 'server url': [
+ """ The LDAP server url """,
+ 'string',
+ 'ldap://example.com'
+ ],
+ 'tls': [
+ " What TLS level show be required " +
+ "(Demand, Allow, Try, Never, NoTLS) ",
+ 'string',
+ 'Demand'
+ ],
+ 'bind dn': [
+ """ User DN to bind as, if empty uses anonymous bind. """,
+ 'string',
+ 'uid=ipsilon,ou=People,dc=example,dc=com'
+ ],
+ 'bind password': [
+ """ Password to use for bind operation """,
+ 'string',
+ 'Password'
+ ],
+ 'user dn template': [
+ """ Template to turn username into DN. """,
+ 'string',
+ 'uid=%(username)s,ou=People,dc=example,dc=com'
+ ],
+ }
+
+ @property
+ def server_url(self):
+ return self.get_config_value('server url')
+
+ @property
+ def tls(self):
+ return self.get_config_value('tls')
+
+ @property
+ def bind_dn(self):
+ return self.get_config_value('bind dn')
+
+ @property
+ def bind_password(self):
+ return self.get_config_value('bind password')
+
+ @property
+ def user_dn_tmpl(self):
+ return self.get_config_value('user dn template')
+
+ def _ldap_bind(self):
+
+ tls = self.tls.lower()
+ tls_req_opt = None
+ if tls == "never":
+ tls_req_opt = ldap.OPT_X_TLS_NEVER
+ elif tls == "demand":
+ tls_req_opt = ldap.OPT_X_TLS_DEMAND
+ elif tls == "allow":
+ tls_req_opt = ldap.OPT_X_TLS_ALLOW
+ elif tls == "try":
+ tls_req_opt = ldap.OPT_X_TLS_TRY
+ if tls_req_opt is not None:
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
+
+ conn = ldap.initialize(self.server_url)
+
+ if tls != "notls":
+ if not self.server_url.startswith("ldaps"):
+ conn.start_tls_s()
+
+ conn.simple_bind_s(self.bind_dn, self.bind_password)
+
+ return conn
+
+ def get_user_data_from_conn(self, conn, dn):
+ result = conn.search_s(dn, ldap.SCOPE_BASE)
+ if result is None or result == []:
+ raise Exception('User object could not be found!')
+ elif len(result) > 1:
+ raise Exception('No unique user object could be found!')
+ return result[0][1]
+
+ def get_user_attrs(self, user):
+ userattrs = None
+ try:
+ conn = self._ldap_bind()
+ dn = self.user_dn_tmpl % {'username': user}
+ userattrs = self.get_user_data_from_conn(conn, dn)
+ except Exception, e: # pylint: disable=broad-except
+ self.error(e)
+
+ return userattrs
+
+
+class Installer(InfoProviderInstaller):
+
+ def __init__(self):
+ super(Installer, self).__init__()
+ self.name = 'nss'
+
+ def install_args(self, group):
+ group.add_argument('--info-ldap', choices=['yes', 'no'], default='no',
+ help='Use LDAP to populate user attrs')
+ group.add_argument('--info-ldap-server-url', action='store',
+ help='LDAP Server Url')
+ group.add_argument('--info-ldap-bind-dn', action='store',
+ help='LDAP Bind DN')
+ group.add_argument('--info-ldap-bind-pwd', action='store',
+ help='LDAP Bind Password')
+ group.add_argument('--info-ldap-user-dn-template', action='store',
+ help='LDAP User DN Template')
+
+ def configure(self, opts):
+ if opts['info_ldap'] != 'yes':
+ return
+
+ # Add configuration data to database
+ po = PluginObject()
+ po.name = 'ldap'
+ po.wipe_data()
+ po.wipe_config_values(self.facility)
+ config = dict()
+ if 'info_ldap_server_url' in opts:
+ config['server url'] = opts['info_ldap_server_url']
+ elif 'ldap_server_url' in opts:
+ config['server url'] = opts['ldap_server_url']
+ config = {'bind dn': opts['info_ldap_bind_dn']}
+ config = {'bind password': opts['info_ldap_bind_pwd']}
+ config = {'user dn template': opts['info_ldap_user_dn_template']}
+ if 'info_ldap_bind_dn' in opts:
+ config['bind dn'] = opts['info_ldap_bind_dn']
+ if 'info_ldap_bind_pwd' in opts:
+ config['bind password'] = opts['info_ldap_bind_pwd']
+ if 'info_ldap_user_dn_template' in opts:
+ config['user dn template'] = opts['info_ldap_user_dn_template']
+ elif 'ldap_bind_dn_template' in opts:
+ config['user dn template'] = opts['ldap_bind_dn_template']
+ config['tls'] = 'Demand'
+ po.set_config(config)
+ po.save_plugin_config(self.facility)
+
+ # Replace global config, only one plugin info can be used
+ po.name = 'global'
+ globalconf = po.get_plugin_config(self.facility)
+ if 'order' in globalconf:
+ order = globalconf['order'].split(',')
+ else:
+ order = []
+ order.append('ldap')
+ globalconf['order'] = ','.join(order)
+ po.set_config(globalconf)
+ po.save_plugin_config(self.facility)
diff --git a/ipsilon/login/authldap.py b/ipsilon/login/authldap.py
new file mode 100755
index 0000000..0d70479
--- /dev/null
+++ b/ipsilon/login/authldap.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Ipsilon Contributors, see COPYING for license
+
+from ipsilon.login.common import LoginFormBase, LoginManagerBase
+from ipsilon.login.common import FACILITY
+from ipsilon.util.plugin import PluginObject
+from ipsilon.util.log import Log
+from ipsilon.info.infoldap import InfoProvider as LDAPInfo
+import ldap
+
+
+class LDAP(LoginFormBase, Log):
+
+ def __init__(self, site, mgr, page):
+ super(LDAP, self).__init__(site, mgr, page)
+ self.ldap_info = None
+
+ def _ldap_connect(self):
+
+ tls = self.lm.tls.lower()
+ tls_req_opt = None
+ if tls == "never":
+ tls_req_opt = ldap.OPT_X_TLS_NEVER
+ elif tls == "demand":
+ tls_req_opt = ldap.OPT_X_TLS_DEMAND
+ elif tls == "allow":
+ tls_req_opt = ldap.OPT_X_TLS_ALLOW
+ elif tls == "try":
+ tls_req_opt = ldap.OPT_X_TLS_TRY
+ if tls_req_opt is not None:
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
+
+ conn = ldap.initialize(self.lm.server_url)
+
+ if tls != "notls":
+ if not self.lm.server_url.startswith("ldaps"):
+ conn.start_tls_s()
+ return conn
+
+ def _authenticate(self, username, password):
+
+ conn = self._ldap_connect()
+ dn = self.lm.bind_dn_tmpl % {'username': username}
+ conn.simple_bind_s(dn, password)
+
+ # Bypass info plugins to optimize data retrieval
+ if self.lm.get_user_info:
+ self.lm.info = None
+
+ if not self.ldap_info:
+ self.ldap_info = LDAPInfo()
+
+ return self.ldap_info.get_user_data_from_conn(conn, dn)
+
+ return None
+
+ def POST(self, *args, **kwargs):
+ username = kwargs.get("login_name")
+ password = kwargs.get("login_password")
+ userattrs = None
+ authed = False
+ errmsg = None
+
+ if username and password:
+ try:
+ userattrs = self._authenticate(username, password)
+ authed = True
+ except Exception, e: # pylint: disable=broad-except
+ errmsg = "Authentication failed"
+ self.error("Exception raised: [%s]" % repr(e))
+ else:
+ errmsg = "Username or password is missing"
+ self.error(errmsg)
+
+ if authed:
+ return self.lm.auth_successful(self.trans, username, 'password',
+ userdata=userattrs)
+
+ context = self.create_tmpl_context(
+ username=username,
+ error=errmsg,
+ error_password=not password,
+ error_username=not username
+ )
+ # pylint: disable=star-args
+ return self._template('login/form.html', **context)
+
+
+class LoginManager(LoginManagerBase):
+
+ def __init__(self, *args, **kwargs):
+ super(LoginManager, self).__init__(*args, **kwargs)
+ self.name = 'ldap'
+ self.path = 'ldap'
+ self.page = None
+ self.ldap_info = None
+ self.service_name = 'ldap'
+ self.description = """
+Form based login Manager that uses a simple bind LDAP operation to perform
+authentication. """
+ self._options = {
+ 'help text': [
+ """ The text shown to guide the user at login time. """,
+ 'string',
+ 'Insert your Username and Password and then submit.'
+ ],
+ 'username text': [
+ """ The text shown to ask for the username in the form. """,
+ 'string',
+ 'Username'
+ ],
+ 'password text': [
+ """ The text shown to ask for the password in the form. """,
+ 'string',
+ 'Password'
+ ],
+ 'server url': [
+ """ The LDAP server url """,
+ 'string',
+ 'ldap://example.com'
+ ],
+ 'tls': [
+ " What TLS level show be required " +
+ "(Demand, Allow, Try, Never, NoTLS) ",
+ 'string',
+ 'Demand'
+ ],
+ 'bind dn template': [
+ """ Template to turn username into DN. """,
+ 'string',
+ 'uid=%(username)s,ou=People,dc=example,dc=com'
+ ],
+ 'get user info': [
+ """ Get user info via ldap directly after auth (Yes/No) """,
+ 'string',
+ 'Yes'
+ ],
+ }
+ self.conf_opt_order = ['server url', 'bind dn template',
+ 'get user info', 'tls', 'username text',
+ 'password text', 'help text']
+
+ @property
+ def help_text(self):
+ return self.get_config_value('help text')
+
+ @property
+ def username_text(self):
+ return self.get_config_value('username text')
+
+ @property
+ def password_text(self):
+ return self.get_config_value('password text')
+
+ @property
+ def server_url(self):
+ return self.get_config_value('server url')
+
+ @property
+ def tls(self):
+ return self.get_config_value('tls')
+
+ @property
+ def get_user_info(self):
+ return (self.get_config_value('get user info').lower() == 'yes')
+
+ @property
+ def bind_dn_tmpl(self):
+ return self.get_config_value('bind dn template')
+
+ def get_tree(self, site):
+ self.page = LDAP(site, self, 'login/ldap')
+ return self.page
+
+
+class Installer(object):
+
+ def __init__(self):
+ self.name = 'ldap'
+ self.ptype = 'login'
+
+ def install_args(self, group):
+ group.add_argument('--ldap', choices=['yes', 'no'], default='no',
+ help='Configure PAM authentication')
+ group.add_argument('--ldap-server-url', action='store',
+ help='LDAP Server Url')
+ group.add_argument('--ldap-bind-dn-template', action='store',
+ help='LDAP Bind DN Template')
+
+ def configure(self, opts):
+ if opts['ldap'] != 'yes':
+ return
+
+ # Add configuration data to database
+ po = PluginObject()
+ po.name = 'ldap'
+ po.wipe_data()
+
+ po.wipe_config_values(FACILITY)
+ config = dict()
+ if 'ldap_server_url' in opts:
+ config['server url'] = opts['ldap_server_url']
+ if 'ldap_bind_dn_template' in opts:
+ config['bind dn template'] = opts['ldap_bind_dn_template']
+ config['tls'] = 'Demand'
+ po.set_config(config)
+ po.save_plugin_config(FACILITY)
+
+ # Update global config to add login plugin
+ po = PluginObject()
+ po.name = 'global'
+ globalconf = po.get_plugin_config(FACILITY)
+ if 'order' in globalconf:
+ order = globalconf['order'].split(',')
+ else:
+ order = []
+ order.append('ldap')
+ globalconf['order'] = ','.join(order)
+ po.set_config(globalconf)
+ po.save_plugin_config(FACILITY)