Fix LDAP plugin configuration checks
[cascardo/ipsilon.git] / ipsilon / login / authldap.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  Ipsilon Contributors, see COPYING for license
4
5 from ipsilon.login.common import LoginFormBase, LoginManagerBase
6 from ipsilon.util.plugin import PluginObject
7 from ipsilon.util.log import Log
8 from ipsilon.util import config as pconfig
9 from ipsilon.info.infoldap import InfoProvider as LDAPInfo
10 import ldap
11
12
13 class LDAP(LoginFormBase, Log):
14
15     def __init__(self, site, mgr, page):
16         super(LDAP, self).__init__(site, mgr, page)
17         self.ldap_info = None
18
19     def _ldap_connect(self):
20
21         tls = self.lm.tls.lower()
22         tls_req_opt = None
23         if tls == "never":
24             tls_req_opt = ldap.OPT_X_TLS_NEVER
25         elif tls == "demand":
26             tls_req_opt = ldap.OPT_X_TLS_DEMAND
27         elif tls == "allow":
28             tls_req_opt = ldap.OPT_X_TLS_ALLOW
29         elif tls == "try":
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)
33
34         conn = ldap.initialize(self.lm.server_url)
35
36         if tls != "notls":
37             if not self.lm.server_url.startswith("ldaps"):
38                 conn.start_tls_s()
39         return conn
40
41     def _authenticate(self, username, password):
42
43         conn = self._ldap_connect()
44         dn = self.lm.bind_dn_tmpl % {'username': username}
45         conn.simple_bind_s(dn, password)
46
47         # Bypass info plugins to optimize data retrieval
48         if self.lm.get_user_info:
49             self.lm.info = None
50
51             if not self.ldap_info:
52                 self.ldap_info = LDAPInfo(self._site)
53
54             return self.ldap_info.get_user_data_from_conn(conn, dn)
55
56         return None
57
58     def POST(self, *args, **kwargs):
59         username = kwargs.get("login_name")
60         password = kwargs.get("login_password")
61         userattrs = None
62         authed = False
63         errmsg = None
64
65         if username and password:
66             try:
67                 userdata = self._authenticate(username, password)
68                 if userdata:
69                     userattrs = dict()
70                     for d, v in userdata.get('userdata', {}).items():
71                         userattrs[d] = v
72                     if 'groups' in userdata:
73                         userattrs['groups'] = userdata['groups']
74                     if 'extras' in userdata:
75                         userattrs['extras'] = userdata['extras']
76                 authed = True
77             except Exception, e:  # pylint: disable=broad-except
78                 errmsg = "Authentication failed"
79                 self.error("Exception raised: [%s]" % repr(e))
80         else:
81             errmsg = "Username or password is missing"
82             self.error(errmsg)
83
84         if authed:
85             return self.lm.auth_successful(self.trans, username, 'password',
86                                            userdata=userattrs)
87
88         context = self.create_tmpl_context(
89             username=username,
90             error=errmsg,
91             error_password=not password,
92             error_username=not username
93         )
94         # pylint: disable=star-args
95         return self._template('login/form.html', **context)
96
97
98 class LoginManager(LoginManagerBase):
99
100     def __init__(self, *args, **kwargs):
101         super(LoginManager, self).__init__(*args, **kwargs)
102         self.name = 'ldap'
103         self.path = 'ldap'
104         self.page = None
105         self.ldap_info = None
106         self.service_name = 'ldap'
107         self.description = """
108 Form based login Manager that uses a simple bind LDAP operation to perform
109 authentication. """
110         self.new_config(
111             self.name,
112             pconfig.String(
113                 'server url',
114                 'The LDAP server url.',
115                 'ldap://example.com'),
116             pconfig.Template(
117                 'bind dn template',
118                 'Template to turn username into DN.',
119                 'uid=%(username)s,ou=People,dc=example,dc=com'),
120             pconfig.Condition(
121                 'get user info',
122                 'Get user info via ldap using user credentials',
123                 True),
124             pconfig.Pick(
125                 'tls',
126                 'What TLS level show be required',
127                 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
128                 'Demand'),
129             pconfig.String(
130                 'username text',
131                 'Text used to ask for the username at login time.',
132                 'Username'),
133             pconfig.String(
134                 'password text',
135                 'Text used to ask for the password at login time.',
136                 'Password'),
137             pconfig.String(
138                 'help text',
139                 'Text used to guide the user at login time.',
140                 'Provide your Username and Password')
141         )
142
143     @property
144     def help_text(self):
145         return self.get_config_value('help text')
146
147     @property
148     def username_text(self):
149         return self.get_config_value('username text')
150
151     @property
152     def password_text(self):
153         return self.get_config_value('password text')
154
155     @property
156     def server_url(self):
157         return self.get_config_value('server url')
158
159     @property
160     def tls(self):
161         return self.get_config_value('tls')
162
163     @property
164     def get_user_info(self):
165         return self.get_config_value('get user info')
166
167     @property
168     def bind_dn_tmpl(self):
169         return self.get_config_value('bind dn template')
170
171     def get_tree(self, site):
172         self.page = LDAP(site, self, 'login/ldap')
173         return self.page
174
175
176 class Installer(object):
177
178     def __init__(self, *pargs):
179         self.name = 'ldap'
180         self.ptype = 'login'
181         self.pargs = pargs
182
183     def install_args(self, group):
184         group.add_argument('--ldap', choices=['yes', 'no'], default='no',
185                            help='Configure PAM authentication')
186         group.add_argument('--ldap-server-url', action='store',
187                            help='LDAP Server Url')
188         group.add_argument('--ldap-bind-dn-template', action='store',
189                            help='LDAP Bind DN Template')
190
191     def configure(self, opts):
192         if opts['ldap'] != 'yes':
193             return
194
195         # Add configuration data to database
196         po = PluginObject(*self.pargs)
197         po.name = 'ldap'
198         po.wipe_data()
199         po.wipe_config_values()
200
201         config = dict()
202         if 'ldap_server_url' in opts:
203             config['server url'] = opts['ldap_server_url']
204         if 'ldap_bind_dn_template' in opts:
205             config['bind dn template'] = opts['ldap_bind_dn_template']
206         config['tls'] = 'Demand'
207         po.save_plugin_config(config)
208
209         # Update global config to add login plugin
210         po.is_enabled = True
211         po.save_enabled_state()