Update Copyright header point to COPYING file
[cascardo/ipsilon.git] / ipsilon / login / authldap.py
1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
2
3 from ipsilon.login.common import LoginFormBase, LoginManagerBase, \
4     LoginManagerInstaller
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
9 import ldap
10 import subprocess
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             base = self.lm.base_dn
55             return self.ldap_info.get_user_data_from_conn(conn, dn, base,
56                                                           username)
57
58         return None
59
60     def POST(self, *args, **kwargs):
61         username = kwargs.get("login_name")
62         password = kwargs.get("login_password")
63         userattrs = None
64         authed = False
65         errmsg = None
66
67         if username and password:
68             try:
69                 userattrs = self._authenticate(username, password)
70                 authed = True
71             except Exception, e:  # pylint: disable=broad-except
72                 errmsg = "Authentication failed"
73                 self.error("Exception raised: [%s]" % repr(e))
74         else:
75             errmsg = "Username or password is missing"
76             self.error(errmsg)
77
78         if authed:
79             return self.lm.auth_successful(self.trans, username, 'password',
80                                            userdata=userattrs)
81
82         context = self.create_tmpl_context(
83             username=username,
84             error=errmsg,
85             error_password=not password,
86             error_username=not username
87         )
88         self.lm.set_auth_error()
89         return self._template('login/form.html', **context)
90
91
92 class LoginManager(LoginManagerBase):
93
94     def __init__(self, *args, **kwargs):
95         super(LoginManager, self).__init__(*args, **kwargs)
96         self.name = 'ldap'
97         self.path = 'ldap'
98         self.page = None
99         self.ldap_info = None
100         self.service_name = 'ldap'
101         self.description = """
102 Form based login Manager that uses a simple bind LDAP operation to perform
103 authentication. """
104         self.new_config(
105             self.name,
106             pconfig.String(
107                 'server url',
108                 'The LDAP server url.',
109                 'ldap://example.com'),
110             pconfig.Template(
111                 'bind dn template',
112                 'Template to turn username into DN.',
113                 'uid=%(username)s,ou=People,dc=example,dc=com'),
114             pconfig.String(
115                 'base dn',
116                 'The base dn to look for users and groups',
117                 'dc=example,dc=com'),
118             pconfig.Condition(
119                 'get user info',
120                 'Get user info via ldap using user credentials',
121                 True),
122             pconfig.Pick(
123                 'tls',
124                 'What TLS level show be required',
125                 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
126                 'Demand'),
127             pconfig.String(
128                 'username text',
129                 'Text used to ask for the username at login time.',
130                 'Username'),
131             pconfig.String(
132                 'password text',
133                 'Text used to ask for the password at login time.',
134                 'Password'),
135             pconfig.String(
136                 'help text',
137                 'Text used to guide the user at login time.',
138                 'Provide your Username and Password')
139         )
140
141     @property
142     def help_text(self):
143         return self.get_config_value('help text')
144
145     @property
146     def username_text(self):
147         return self.get_config_value('username text')
148
149     @property
150     def password_text(self):
151         return self.get_config_value('password text')
152
153     @property
154     def server_url(self):
155         return self.get_config_value('server url')
156
157     @property
158     def tls(self):
159         return self.get_config_value('tls')
160
161     @property
162     def get_user_info(self):
163         return self.get_config_value('get user info')
164
165     @property
166     def bind_dn_tmpl(self):
167         return self.get_config_value('bind dn template')
168
169     @property
170     def base_dn(self):
171         return self.get_config_value('base dn')
172
173     def get_tree(self, site):
174         self.page = LDAP(site, self, 'login/ldap')
175         return self.page
176
177
178 class Installer(LoginManagerInstaller):
179
180     def __init__(self, *pargs):
181         super(Installer, self).__init__()
182         self.name = 'ldap'
183         self.pargs = pargs
184
185     def install_args(self, group):
186         group.add_argument('--ldap', choices=['yes', 'no'], default='no',
187                            help='Configure LDAP authentication')
188         group.add_argument('--ldap-server-url', action='store',
189                            help='LDAP Server Url')
190         group.add_argument('--ldap-bind-dn-template', action='store',
191                            help='LDAP Bind DN Template')
192         group.add_argument('--ldap-tls-level', action='store', default=None,
193                            help='LDAP TLS level')
194         group.add_argument('--ldap-base-dn', action='store',
195                            help='LDAP Base DN')
196
197     def configure(self, opts):
198         if opts['ldap'] != 'yes':
199             return
200
201         # Add configuration data to database
202         po = PluginObject(*self.pargs)
203         po.name = 'ldap'
204         po.wipe_data()
205         po.wipe_config_values()
206
207         config = dict()
208         if 'ldap_server_url' in opts:
209             config['server url'] = opts['ldap_server_url']
210         if 'ldap_bind_dn_template' in opts:
211             config['bind dn template'] = opts['ldap_bind_dn_template']
212         if 'ldap_tls_level' in opts and opts['ldap_tls_level'] is not None:
213             config['tls'] = opts['ldap_tls_level']
214         else:
215             config['tls'] = 'Demand'
216         if 'ldap_base_dn' in opts and opts['ldap_base_dn'] is not None:
217             config['base dn'] = opts['ldap_base_dn']
218         po.save_plugin_config(config)
219
220         # Update global config to add login plugin
221         po.is_enabled = True
222         po.save_enabled_state()
223
224         # For selinux enabled platforms permit httpd to connect to ldap,
225         # ignore if it fails
226         try:
227             subprocess.call(['/usr/sbin/setsebool', '-P',
228                              'httpd_can_connect_ldap=on'])
229         except Exception:  # pylint: disable=broad-except
230             pass