0d704796b8bcf585c2809ba212a9c4a227d9f328
[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                 userattrs = self._authenticate(username, password)
68                 authed = True
69             except Exception, e:  # pylint: disable=broad-except
70                 errmsg = "Authentication failed"
71                 self.error("Exception raised: [%s]" % repr(e))
72         else:
73             errmsg = "Username or password is missing"
74             self.error(errmsg)
75
76         if authed:
77             return self.lm.auth_successful(self.trans, username, 'password',
78                                            userdata=userattrs)
79
80         context = self.create_tmpl_context(
81             username=username,
82             error=errmsg,
83             error_password=not password,
84             error_username=not username
85         )
86         # pylint: disable=star-args
87         return self._template('login/form.html', **context)
88
89
90 class LoginManager(LoginManagerBase):
91
92     def __init__(self, *args, **kwargs):
93         super(LoginManager, self).__init__(*args, **kwargs)
94         self.name = 'ldap'
95         self.path = 'ldap'
96         self.page = None
97         self.ldap_info = None
98         self.service_name = 'ldap'
99         self.description = """
100 Form based login Manager that uses a simple bind LDAP operation to perform
101 authentication. """
102         self._options = {
103             'help text': [
104                 """ The text shown to guide the user at login time. """,
105                 'string',
106                 'Insert your Username and Password and then submit.'
107             ],
108             'username text': [
109                 """ The text shown to ask for the username in the form. """,
110                 'string',
111                 'Username'
112             ],
113             'password text': [
114                 """ The text shown to ask for the password in the form. """,
115                 'string',
116                 'Password'
117             ],
118             'server url': [
119                 """ The LDAP server url """,
120                 'string',
121                 'ldap://example.com'
122             ],
123             'tls': [
124                 " What TLS level show be required " +
125                 "(Demand, Allow, Try, Never, NoTLS) ",
126                 'string',
127                 'Demand'
128             ],
129             'bind dn template': [
130                 """ Template to turn username into DN. """,
131                 'string',
132                 'uid=%(username)s,ou=People,dc=example,dc=com'
133             ],
134             'get user info': [
135                 """ Get user info via ldap directly after auth (Yes/No) """,
136                 'string',
137                 'Yes'
138             ],
139         }
140         self.conf_opt_order = ['server url', 'bind dn template',
141                                'get user info', 'tls', 'username text',
142                                'password text', 'help text']
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.set_config(config)
208         po.save_plugin_config(FACILITY)
209
210         # Update global config to add login plugin
211         po = PluginObject()
212         po.name = 'global'
213         globalconf = po.get_plugin_config(FACILITY)
214         if 'order' in globalconf:
215             order = globalconf['order'].split(',')
216         else:
217             order = []
218         order.append('ldap')
219         globalconf['order'] = ','.join(order)
220         po.set_config(globalconf)
221         po.save_plugin_config(FACILITY)