Add very simple LDAP authentication plugin
[cascardo/ipsilon.git] / ipsilon / info / infoldap.py
diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py
new file mode 100755 (executable)
index 0000000..6d710bd
--- /dev/null
@@ -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)