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