1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
3 from ipsilon.login.common import LoginFormBase, LoginManagerBase, \
5 from ipsilon.util.plugin import PluginObject
6 from ipsilon.util.log import Log
7 from ipsilon.util import config as pconfig
8 from ipsilon.info.infoldap import InfoProvider as LDAPInfo
13 class LDAP(LoginFormBase, Log):
15 def __init__(self, site, mgr, page):
16 super(LDAP, self).__init__(site, mgr, page)
19 def _ldap_connect(self):
21 tls = self.lm.tls.lower()
24 tls_req_opt = ldap.OPT_X_TLS_NEVER
26 tls_req_opt = ldap.OPT_X_TLS_DEMAND
28 tls_req_opt = ldap.OPT_X_TLS_ALLOW
30 tls_req_opt = ldap.OPT_X_TLS_TRY
31 if tls_req_opt is not None:
32 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
34 conn = ldap.initialize(self.lm.server_url)
37 if not self.lm.server_url.startswith("ldaps"):
41 def _authenticate(self, username, password):
43 conn = self._ldap_connect()
44 dn = self.lm.bind_dn_tmpl % {'username': username}
45 conn.simple_bind_s(dn, password)
47 # Bypass info plugins to optimize data retrieval
48 if self.lm.get_user_info:
51 if not self.ldap_info:
52 self.ldap_info = LDAPInfo(self._site)
54 base = self.lm.base_dn
55 return self.ldap_info.get_user_data_from_conn(conn, dn, base,
60 def POST(self, *args, **kwargs):
61 username = kwargs.get("login_name")
62 password = kwargs.get("login_password")
67 if username and password:
69 userattrs = self._authenticate(username, password)
71 except ldap.INVALID_CREDENTIALS as e:
72 errmsg = "Authentication failed"
74 except ldap.LDAPError as e:
75 errmsg = 'Internal system error'
76 if isinstance(e, ldap.TIMEOUT):
77 self.error('LDAP request timed out')
79 desc = e.args[0]['desc'].strip()
80 info = e.args[0].get('info', '').strip()
81 self.error("%s: %s %s" % (e.__class__.__name__,
83 except Exception as e: # pylint: disable=broad-except
84 errmsg = 'Internal system error'
85 self.error("Exception raised: [%s]" % repr(e))
87 self.error("Username or password is missing")
90 return self.lm.auth_successful(self.trans, username, 'password',
93 context = self.create_tmpl_context(
96 error_password=not password,
97 error_username=not username
99 self.lm.set_auth_error()
100 return self._template('login/form.html', **context)
103 class LoginManager(LoginManagerBase):
105 def __init__(self, *args, **kwargs):
106 super(LoginManager, self).__init__(*args, **kwargs)
110 self.ldap_info = None
111 self.service_name = 'ldap'
112 self.description = """
113 Form based login Manager that uses a simple bind LDAP operation to perform
119 'The LDAP server url.',
120 'ldap://example.com'),
123 'Template to turn username into DN.',
124 'uid=%(username)s,ou=People,dc=example,dc=com'),
127 'The base dn to look for users and groups',
128 'dc=example,dc=com'),
131 'Get user info via ldap using user credentials',
135 'What TLS level show be required',
136 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
140 'Text used to ask for the username at login time.',
144 'Text used to ask for the password at login time.',
148 'Text used to guide the user at login time.',
149 'Provide your Username and Password')
154 return self.get_config_value('help text')
157 def username_text(self):
158 return self.get_config_value('username text')
161 def password_text(self):
162 return self.get_config_value('password text')
165 def server_url(self):
166 return self.get_config_value('server url')
170 return self.get_config_value('tls')
173 def get_user_info(self):
174 return self.get_config_value('get user info')
177 def bind_dn_tmpl(self):
178 return self.get_config_value('bind dn template')
182 return self.get_config_value('base dn')
184 def get_tree(self, site):
185 self.page = LDAP(site, self, 'login/ldap')
189 class Installer(LoginManagerInstaller):
191 def __init__(self, *pargs):
192 super(Installer, self).__init__()
196 def install_args(self, group):
197 group.add_argument('--ldap', choices=['yes', 'no'], default='no',
198 help='Configure LDAP authentication')
199 group.add_argument('--ldap-server-url', action='store',
200 help='LDAP Server Url')
201 group.add_argument('--ldap-bind-dn-template', action='store',
202 help='LDAP Bind DN Template')
203 group.add_argument('--ldap-tls-level', action='store', default=None,
204 help='LDAP TLS level')
205 group.add_argument('--ldap-base-dn', action='store',
208 def configure(self, opts, changes):
209 if opts['ldap'] != 'yes':
212 # Add configuration data to database
213 po = PluginObject(*self.pargs)
216 po.wipe_config_values()
219 if 'ldap_server_url' in opts:
220 config['server url'] = opts['ldap_server_url']
221 if 'ldap_bind_dn_template' in opts:
222 config['bind dn template'] = opts['ldap_bind_dn_template']
223 if 'ldap_tls_level' in opts and opts['ldap_tls_level'] is not None:
224 config['tls'] = opts['ldap_tls_level']
226 config['tls'] = 'Demand'
227 if 'ldap_base_dn' in opts and opts['ldap_base_dn'] is not None:
228 config['base dn'] = opts['ldap_base_dn']
229 po.save_plugin_config(config)
231 # Update global config to add login plugin
233 po.save_enabled_state()
235 # For selinux enabled platforms permit httpd to connect to ldap,
238 subprocess.call(['/usr/sbin/setsebool', '-P',
239 'httpd_can_connect_ldap=on'])
240 except Exception: # pylint: disable=broad-except