set SELinux boolean httpd_can_connect_ldap when install infolap and authldap
[cascardo/ipsilon.git] / ipsilon / login / authldap.py
1 # Copyright (C) 2014  Ipsilon Contributors, see COPYING for license
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             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         self.lm.set_auth_error()
87         # pylint: disable=star-args
88         return self._template('login/form.html', **context)
89
90
91 class LoginManager(LoginManagerBase):
92
93     def __init__(self, *args, **kwargs):
94         super(LoginManager, self).__init__(*args, **kwargs)
95         self.name = 'ldap'
96         self.path = 'ldap'
97         self.page = None
98         self.ldap_info = None
99         self.service_name = 'ldap'
100         self.description = """
101 Form based login Manager that uses a simple bind LDAP operation to perform
102 authentication. """
103         self.new_config(
104             self.name,
105             pconfig.String(
106                 'server url',
107                 'The LDAP server url.',
108                 'ldap://example.com'),
109             pconfig.Template(
110                 'bind dn template',
111                 'Template to turn username into DN.',
112                 'uid=%(username)s,ou=People,dc=example,dc=com'),
113             pconfig.Condition(
114                 'get user info',
115                 'Get user info via ldap using user credentials',
116                 True),
117             pconfig.Pick(
118                 'tls',
119                 'What TLS level show be required',
120                 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
121                 'Demand'),
122             pconfig.String(
123                 'username text',
124                 'Text used to ask for the username at login time.',
125                 'Username'),
126             pconfig.String(
127                 'password text',
128                 'Text used to ask for the password at login time.',
129                 'Password'),
130             pconfig.String(
131                 'help text',
132                 'Text used to guide the user at login time.',
133                 'Provide your Username and Password')
134         )
135
136     @property
137     def help_text(self):
138         return self.get_config_value('help text')
139
140     @property
141     def username_text(self):
142         return self.get_config_value('username text')
143
144     @property
145     def password_text(self):
146         return self.get_config_value('password text')
147
148     @property
149     def server_url(self):
150         return self.get_config_value('server url')
151
152     @property
153     def tls(self):
154         return self.get_config_value('tls')
155
156     @property
157     def get_user_info(self):
158         return self.get_config_value('get user info')
159
160     @property
161     def bind_dn_tmpl(self):
162         return self.get_config_value('bind dn template')
163
164     def get_tree(self, site):
165         self.page = LDAP(site, self, 'login/ldap')
166         return self.page
167
168
169 class Installer(LoginManagerInstaller):
170
171     def __init__(self, *pargs):
172         super(Installer, self).__init__()
173         self.name = 'ldap'
174         self.pargs = pargs
175
176     def install_args(self, group):
177         group.add_argument('--ldap', choices=['yes', 'no'], default='no',
178                            help='Configure LDAP authentication')
179         group.add_argument('--ldap-server-url', action='store',
180                            help='LDAP Server Url')
181         group.add_argument('--ldap-bind-dn-template', action='store',
182                            help='LDAP Bind DN Template')
183
184     def configure(self, opts):
185         if opts['ldap'] != 'yes':
186             return
187
188         # Add configuration data to database
189         po = PluginObject(*self.pargs)
190         po.name = 'ldap'
191         po.wipe_data()
192         po.wipe_config_values()
193
194         config = dict()
195         if 'ldap_server_url' in opts:
196             config['server url'] = opts['ldap_server_url']
197         if 'ldap_bind_dn_template' in opts:
198             config['bind dn template'] = opts['ldap_bind_dn_template']
199         config['tls'] = 'Demand'
200         po.save_plugin_config(config)
201
202         # Update global config to add login plugin
203         po.is_enabled = True
204         po.save_enabled_state()
205
206         # For selinux enabled platforms permit httpd to connect to ldap,
207         # ignore if it fails
208         try:
209             subprocess.call(['/usr/sbin/setsebool', '-P',
210                              'httpd_can_connect_ldap=on'])
211         except Exception:  # pylint: disable=broad-except
212             pass