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
14 def ldap_connect(server_url, tls):
18 tls_req_opt = ldap.OPT_X_TLS_NEVER
20 tls_req_opt = ldap.OPT_X_TLS_DEMAND
22 tls_req_opt = ldap.OPT_X_TLS_ALLOW
24 tls_req_opt = ldap.OPT_X_TLS_TRY
25 if tls_req_opt is not None:
26 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
28 conn = ldap.initialize(server_url)
31 if not server_url.startswith("ldaps"):
37 class LDAP(LoginFormBase, Log):
39 def __init__(self, site, mgr, page):
40 super(LDAP, self).__init__(site, mgr, page)
43 def _ldap_connect(self):
44 return ldap_connect(self.lm.server_url, self.lm.tls)
46 def _authenticate(self, username, password):
48 conn = self._ldap_connect()
49 dn = self.lm.bind_dn_tmpl % {'username': username}
50 conn.simple_bind_s(dn, password)
52 # Bypass info plugins to optimize data retrieval
53 if self.lm.get_user_info:
56 if not self.ldap_info:
57 self.ldap_info = LDAPInfo(self._site)
59 base = self.lm.base_dn
60 return self.ldap_info.get_user_data_from_conn(conn, dn, base,
65 def POST(self, *args, **kwargs):
66 username = kwargs.get("login_name")
67 password = kwargs.get("login_password")
72 if username and password:
74 userattrs = self._authenticate(username, password)
76 except ldap.INVALID_CREDENTIALS as e:
77 errmsg = "Authentication failed"
79 except ldap.LDAPError as e:
80 errmsg = 'Internal system error'
81 if isinstance(e, ldap.TIMEOUT):
82 self.error('LDAP request timed out')
84 desc = e.args[0]['desc'].strip()
85 info = e.args[0].get('info', '').strip()
86 self.error("%s: %s %s" % (e.__class__.__name__,
88 except Exception as e: # pylint: disable=broad-except
89 errmsg = 'Internal system error'
90 self.error("Exception raised: [%s]" % repr(e))
92 self.error("Username or password is missing")
95 return self.lm.auth_successful(self.trans, username, 'password',
98 context = self.create_tmpl_context(
101 error_password=not password,
102 error_username=not username
104 self.lm.set_auth_error()
105 return self._template('login/form.html', **context)
108 class LoginManager(LoginManagerBase):
110 def __init__(self, *args, **kwargs):
111 super(LoginManager, self).__init__(*args, **kwargs)
115 self.ldap_info = None
116 self.service_name = 'ldap'
117 self.description = """
118 Form based login Manager that uses a simple bind LDAP operation to perform
124 'The LDAP server url.',
125 'ldap://example.com'),
128 'Template to turn username into DN.',
129 'uid=%(username)s,ou=People,dc=example,dc=com'),
132 'The base dn to look for users and groups',
133 'dc=example,dc=com'),
136 'Get user info via ldap using user credentials',
140 'What TLS level show be required',
141 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
145 'Text used to ask for the username at login time.',
149 'Text used to ask for the password at login time.',
153 'Text used to guide the user at login time.',
154 'Provide your Username and Password')
159 return self.get_config_value('help text')
162 def username_text(self):
163 return self.get_config_value('username text')
166 def password_text(self):
167 return self.get_config_value('password text')
170 def server_url(self):
171 return self.get_config_value('server url')
175 return self.get_config_value('tls')
178 def get_user_info(self):
179 return self.get_config_value('get user info')
182 def bind_dn_tmpl(self):
183 return self.get_config_value('bind dn template')
187 return self.get_config_value('base dn')
189 def get_tree(self, site):
190 self.page = LDAP(site, self, 'login/ldap')
194 class Installer(LoginManagerInstaller):
196 def __init__(self, *pargs):
197 super(Installer, self).__init__()
201 def install_args(self, group):
202 group.add_argument('--ldap', choices=['yes', 'no'], default='no',
203 help='Configure LDAP authentication')
204 group.add_argument('--ldap-server-url', action='store',
205 help='LDAP Server Url')
206 group.add_argument('--ldap-bind-dn-template', action='store',
207 help='LDAP Bind DN Template')
208 group.add_argument('--ldap-tls-level', default='Demand',
209 choices=['Demand', 'Allow', 'Try', 'Never',
211 help='LDAP TLS level')
212 group.add_argument('--ldap-base-dn', action='store',
215 def configure(self, opts, changes):
216 if opts['ldap'] != 'yes':
219 # Add configuration data to database
220 po = PluginObject(*self.pargs)
223 po.wipe_config_values()
226 if 'ldap_server_url' in opts:
227 config['server url'] = opts['ldap_server_url']
229 logging.error('LDAP Server URL is required')
231 if 'ldap_bind_dn_template' in opts:
233 opts['ldap_bind_dn_template'] % {'username': 'test'}
236 'Bind DN template does not contain %(username)s'
239 except ValueError as e:
241 'Invalid syntax in Bind DN template: %s ',
245 config['bind dn template'] = opts['ldap_bind_dn_template']
246 if 'ldap_tls_level' in opts and opts['ldap_tls_level'] is not None:
247 config['tls'] = opts['ldap_tls_level']
249 config['tls'] = 'Demand'
250 if 'ldap_base_dn' in opts and opts['ldap_base_dn'] is not None:
251 config['base dn'] = opts['ldap_base_dn']
252 test_dn = config['base dn']
254 # default set in the config object
255 test_dn = 'dc=example,dc=com'
257 # Test the LDAP connection anonymously
259 lh = ldap_connect(config['server url'], config['tls'])
260 lh.simple_bind_s('', '')
261 lh.search_s(test_dn, ldap.SCOPE_BASE,
262 attrlist=['objectclasses'])
263 except ldap.INSUFFICIENT_ACCESS:
264 logging.warn('Anonymous access not allowed, continuing')
265 except ldap.UNWILLING_TO_PERFORM: # probably minSSF issue
266 logging.warn('LDAP server unwilling to perform, expect issues')
267 except ldap.SERVER_DOWN:
268 logging.warn('LDAP server is down')
269 except ldap.NO_SUCH_OBJECT:
270 logging.error('Base DN not found')
272 except ldap.LDAPError as e:
276 po.save_plugin_config(config)
278 # Update global config to add login plugin
280 po.save_enabled_state()
282 # For selinux enabled platforms permit httpd to connect to ldap,
285 subprocess.call(['/usr/sbin/setsebool', '-P',
286 'httpd_can_connect_ldap=on'])
287 except Exception: # pylint: disable=broad-except