ed75e917dcefb0f6944759e1c64bd11bf32a4022
[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.login.common import FACILITY
7 from ipsilon.util.plugin import PluginObject
8 from ipsilon.util.log import Log
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()
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._options = {
111             'help text': [
112                 """ The text shown to guide the user at login time. """,
113                 'string',
114                 'Insert your Username and Password and then submit.'
115             ],
116             'username text': [
117                 """ The text shown to ask for the username in the form. """,
118                 'string',
119                 'Username'
120             ],
121             'password text': [
122                 """ The text shown to ask for the password in the form. """,
123                 'string',
124                 'Password'
125             ],
126             'server url': [
127                 """ The LDAP server url """,
128                 'string',
129                 'ldap://example.com'
130             ],
131             'tls': [
132                 " What TLS level show be required " +
133                 "(Demand, Allow, Try, Never, NoTLS) ",
134                 'string',
135                 'Demand'
136             ],
137             'bind dn template': [
138                 """ Template to turn username into DN. """,
139                 'string',
140                 'uid=%(username)s,ou=People,dc=example,dc=com'
141             ],
142             'get user info': [
143                 """ Get user info via ldap directly after auth (Yes/No) """,
144                 'string',
145                 'Yes'
146             ],
147         }
148         self.conf_opt_order = ['server url', 'bind dn template',
149                                'get user info', 'tls', 'username text',
150                                'password text', 'help text']
151
152     @property
153     def help_text(self):
154         return self.get_config_value('help text')
155
156     @property
157     def username_text(self):
158         return self.get_config_value('username text')
159
160     @property
161     def password_text(self):
162         return self.get_config_value('password text')
163
164     @property
165     def server_url(self):
166         return self.get_config_value('server url')
167
168     @property
169     def tls(self):
170         return self.get_config_value('tls')
171
172     @property
173     def get_user_info(self):
174         return (self.get_config_value('get user info').lower() == 'yes')
175
176     @property
177     def bind_dn_tmpl(self):
178         return self.get_config_value('bind dn template')
179
180     def get_tree(self, site):
181         self.page = LDAP(site, self, 'login/ldap')
182         return self.page
183
184
185 class Installer(object):
186
187     def __init__(self):
188         self.name = 'ldap'
189         self.ptype = 'login'
190
191     def install_args(self, group):
192         group.add_argument('--ldap', choices=['yes', 'no'], default='no',
193                            help='Configure PAM authentication')
194         group.add_argument('--ldap-server-url', action='store',
195                            help='LDAP Server Url')
196         group.add_argument('--ldap-bind-dn-template', action='store',
197                            help='LDAP Bind DN Template')
198
199     def configure(self, opts):
200         if opts['ldap'] != 'yes':
201             return
202
203         # Add configuration data to database
204         po = PluginObject()
205         po.name = 'ldap'
206         po.wipe_data()
207
208         po.wipe_config_values(FACILITY)
209         config = dict()
210         if 'ldap_server_url' in opts:
211             config['server url'] = opts['ldap_server_url']
212         if 'ldap_bind_dn_template' in opts:
213             config['bind dn template'] = opts['ldap_bind_dn_template']
214         config['tls'] = 'Demand'
215         po.save_plugin_config(FACILITY, config)
216
217         # Update global config to add login plugin
218         po = PluginObject()
219         po.name = 'global'
220         globalconf = po.get_plugin_config(FACILITY)
221         if 'order' in globalconf:
222             order = globalconf['order'].split(',')
223         else:
224             order = []
225         order.append('ldap')
226         globalconf['order'] = ','.join(order)
227         po.save_plugin_config(FACILITY, globalconf)