pylint 1.4.3 version fixes
[cascardo/ipsilon.git] / ipsilon / info / infoldap.py
1 # Copyright (C) 2014 Ipsilon Project Contributors
2 #
3 # See the file named COPYING for the project license
4
5 from ipsilon.info.common import InfoProviderBase
6 from ipsilon.info.common import InfoProviderInstaller
7 from ipsilon.util.plugin import PluginObject
8 from ipsilon.util.policy import Policy
9 from ipsilon.util import config as pconfig
10 import ldap
11 import subprocess
12
13
14 # TODO: fetch mapping from configuration
15 ldap_mapping = [
16     ['cn', 'fullname'],
17     ['commonname', 'fullname'],
18     ['sn', 'surname'],
19     ['mail', 'email'],
20     ['destinationindicator', 'country'],
21     ['postalcode', 'postcode'],
22     ['st', 'state'],
23     ['statetorprovincename', 'state'],
24     ['streetaddress', 'street'],
25     ['telephonenumber', 'phone'],
26 ]
27
28
29 class InfoProvider(InfoProviderBase):
30
31     def __init__(self, *pargs):
32         super(InfoProvider, self).__init__(*pargs)
33         self.mapper = Policy(ldap_mapping)
34         self.name = 'ldap'
35         self.description = """
36 Info plugin that uses LDAP to retrieve user data. """
37         self.new_config(
38             self.name,
39             pconfig.String(
40                 'server url',
41                 'The LDAP server url.',
42                 'ldap://example.com'),
43             pconfig.Template(
44                 'user dn template',
45                 'Template to turn username into DN.',
46                 'uid=%(username)s,ou=People,dc=example,dc=com'),
47             pconfig.Pick(
48                 'tls',
49                 'What TLS level show be required',
50                 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
51                 'Demand'),
52             pconfig.String(
53                 'bind dn',
54                 'DN to bind as, if empty uses anonymous bind.',
55                 'uid=ipsilon,ou=People,dc=example,dc=com'),
56             pconfig.String(
57                 'bind password',
58                 'Password to use for bind operation'),
59             pconfig.String(
60                 'base dn',
61                 'The base dn to look for users and groups',
62                 'dc=example,dc=com'),
63         )
64
65     @property
66     def server_url(self):
67         return self.get_config_value('server url')
68
69     @property
70     def tls(self):
71         return self.get_config_value('tls')
72
73     @property
74     def bind_dn(self):
75         return self.get_config_value('bind dn')
76
77     @property
78     def bind_password(self):
79         return self.get_config_value('bind password')
80
81     @property
82     def user_dn_tmpl(self):
83         return self.get_config_value('user dn template')
84
85     @property
86     def base_dn(self):
87         return self.get_config_value('base dn')
88
89     def _ldap_bind(self):
90
91         tls = self.tls.lower()
92         tls_req_opt = None
93         if tls == "never":
94             tls_req_opt = ldap.OPT_X_TLS_NEVER
95         elif tls == "demand":
96             tls_req_opt = ldap.OPT_X_TLS_DEMAND
97         elif tls == "allow":
98             tls_req_opt = ldap.OPT_X_TLS_ALLOW
99         elif tls == "try":
100             tls_req_opt = ldap.OPT_X_TLS_TRY
101         if tls_req_opt is not None:
102             ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
103
104         conn = ldap.initialize(self.server_url)
105
106         if tls != "notls":
107             if not self.server_url.startswith("ldaps"):
108                 conn.start_tls_s()
109
110         conn.simple_bind_s(self.bind_dn, self.bind_password)
111
112         return conn
113
114     def _get_user_data(self, conn, dn):
115         result = conn.search_s(dn, ldap.SCOPE_BASE)
116         if result is None or result == []:
117             raise Exception('User object could not be found!')
118         elif len(result) > 1:
119             raise Exception('No unique user object could be found!')
120         data = dict()
121         for name, value in result[0][1].iteritems():
122             if isinstance(value, list) and len(value) == 1:
123                 value = value[0]
124             data[name] = value
125         return data
126
127     def _get_user_groups(self, conn, base, username):
128         # TODO: fixme to support RFC2307bis schemas
129         results = conn.search_s(base, ldap.SCOPE_SUBTREE,
130                                 filterstr='memberuid=%s' % username)
131         if results is None or results == []:
132             self.debug('No groups for %s' % username)
133             return []
134         groups = []
135         for r in results:
136             if 'cn' in r[1]:
137                 groups.append(r[1]['cn'][0])
138         return groups
139
140     def get_user_data_from_conn(self, conn, dn, base, username):
141         reply = dict()
142         try:
143             ldapattrs = self._get_user_data(conn, dn)
144             self.debug(ldapattrs)
145             userattrs, extras = self.mapper.map_attributes(ldapattrs)
146             groups = self._get_user_groups(conn, base, username)
147             reply = userattrs
148             reply['_groups'] = groups
149             reply['_extras'] = {'ldap': extras}
150         except Exception, e:  # pylint: disable=broad-except
151             self.error(e)
152
153         return reply
154
155     def get_user_attrs(self, user):
156         try:
157             conn = self._ldap_bind()
158             dn = self.user_dn_tmpl % {'username': user}
159             base = self.base_dn
160             return self.get_user_data_from_conn(conn, dn, base, user)
161         except Exception, e:  # pylint: disable=broad-except
162             self.error(e)
163             return {}
164
165
166 class Installer(InfoProviderInstaller):
167
168     def __init__(self, *pargs):
169         super(Installer, self).__init__()
170         self.name = 'ldap'
171         self.pargs = pargs
172
173     def install_args(self, group):
174         group.add_argument('--info-ldap', choices=['yes', 'no'], default='no',
175                            help='Use LDAP to populate user attrs')
176         group.add_argument('--info-ldap-server-url', action='store',
177                            help='LDAP Server Url')
178         group.add_argument('--info-ldap-bind-dn', action='store',
179                            help='LDAP Bind DN')
180         group.add_argument('--info-ldap-bind-pwd', action='store',
181                            help='LDAP Bind Password')
182         group.add_argument('--info-ldap-user-dn-template', action='store',
183                            help='LDAP User DN Template')
184         group.add_argument('--info-ldap-base-dn', action='store',
185                            help='LDAP Base DN')
186
187     def configure(self, opts):
188         if opts['info_ldap'] != 'yes':
189             return
190
191         # Add configuration data to database
192         po = PluginObject(*self.pargs)
193         po.name = 'ldap'
194         po.wipe_data()
195         po.wipe_config_values()
196         config = dict()
197         if 'info_ldap_server_url' in opts:
198             config['server url'] = opts['info_ldap_server_url']
199         elif 'ldap_server_url' in opts:
200             config['server url'] = opts['ldap_server_url']
201         if 'info_ldap_bind_dn' in opts:
202             config['bind dn'] = opts['info_ldap_bind_dn']
203         if 'info_ldap_bind_pwd' in opts:
204             config['bind password'] = opts['info_ldap_bind_pwd']
205         if 'info_ldap_user_dn_template' in opts:
206             config['user dn template'] = opts['info_ldap_user_dn_template']
207         elif 'ldap_bind_dn_template' in opts:
208             config['user dn template'] = opts['ldap_bind_dn_template']
209         if 'info_ldap_tls_level' in opts and opts['info_ldap_tls_level']:
210             config['tls'] = opts['info_ldap_tls_level']
211         elif 'ldap_tls_level' in opts and opts['ldap_tls_level']:
212             config['tls'] = opts['ldap_tls_level']
213         else:
214             config['tls'] = 'Demand'
215         if 'info_ldap_base_dn' in opts and opts['info_ldap_base_dn']:
216             config['base dn'] = opts['info_ldap_base_dn']
217         elif 'ldap_base_dn' in opts and opts['ldap_base_dn']:
218             config['base dn'] = opts['ldap_base_dn']
219         po.save_plugin_config(config)
220
221         # Update global config to add info plugin
222         po.is_enabled = True
223         po.save_enabled_state()
224
225         # For selinux enabled platforms permit httpd to connect to ldap,
226         # ignore if it fails
227         try:
228             subprocess.call(['/usr/sbin/setsebool', '-P',
229                              'httpd_can_connect_ldap=on'])
230         except Exception:  # pylint: disable=broad-except
231             pass