pylint 1.4.3 version fixes
[cascardo/ipsilon.git] / ipsilon / info / infosssd.py
1 # Copyright (C) 2014 Ipsilon Project Contributors
2 #
3 # See the file named COPYING for the project license
4
5 # Info plugin for mod_lookup_identity Apache module via SSSD
6 # http://www.adelton.com/apache/mod_lookup_identity/
7
8 from ipsilon.info.common import InfoProviderBase
9 from ipsilon.info.common import InfoProviderInstaller
10 from ipsilon.util.plugin import PluginObject
11 from ipsilon.util.policy import Policy
12 from ipsilon.util import config as pconfig
13 from string import Template
14 import cherrypy
15 import time
16 import subprocess
17 import SSSDConfig
18 import logging
19
20 SSSD_CONF = '/etc/sssd/sssd.conf'
21
22 # LDAP attributes to tell SSSD to fetch over the InfoPipe
23 SSSD_ATTRS = ['mail',
24               'street',
25               'locality',
26               'postalCode',
27               'telephoneNumber',
28               'givenname',
29               'sn']
30
31 # Map the mod_lookup_identity env variables to Ipsilon. The inverse of
32 # this is in the httpd template.
33 sssd_mapping = [
34     ['REMOTE_USER_GECOS', 'fullname'],
35     ['REMOTE_USER_EMAIL', 'email'],
36     ['REMOTE_USER_FIRSTNAME', 'givenname'],
37     ['REMOTE_USER_LASTNAME', 'surname'],
38     ['REMOTE_USER_STREET', 'street'],
39     ['REMOTE_USER_STATE', 'state'],
40     ['REMOTE_USER_POSTALCODE', 'postcode'],
41     ['REMOTE_USER_TELEPHONENUMBER', 'phone'],
42 ]
43
44
45 class InfoProvider(InfoProviderBase):
46
47     def __init__(self, *pargs):
48         super(InfoProvider, self).__init__(*pargs)
49         self.mapper = Policy(sssd_mapping)
50         self.name = 'sssd'
51         self.new_config(
52             self.name,
53             pconfig.Condition(
54                 'preconfigured',
55                 'SSSD can only be used when pre-configured',
56                 False),
57         )
58
59     def _get_user_data(self, user):
60         reply = dict()
61         groups = []
62         expectgroups = int(cherrypy.request.wsgi_environ.get(
63             'REMOTE_USER_GROUP_N', 0))
64         for key in cherrypy.request.wsgi_environ:
65             if key.startswith('REMOTE_USER_'):
66                 if key == 'REMOTE_USER_GROUP_N':
67                     continue
68                 if key.startswith('REMOTE_USER_GROUP_'):
69                     groups.append(cherrypy.request.wsgi_environ[key])
70                 else:
71                     reply[key] = cherrypy.request.wsgi_environ[key]
72         if len(groups) != expectgroups:
73             self.error('Number of groups expected was not found. Expected'
74                        ' %d got %d' % (expectgroups, len(groups)))
75         return reply, groups
76
77     def get_user_attrs(self, user):
78         reply = dict()
79         try:
80             attrs, groups = self._get_user_data(user)
81             userattrs, extras = self.mapper.map_attributes(attrs)
82             reply = userattrs
83             reply['_groups'] = groups
84             reply['_extras'] = {'sssd': extras}
85
86         except KeyError:
87             pass
88
89         return reply
90
91     def save_plugin_config(self, *args, **kwargs):
92         raise ValueError('Configuration cannot be modified live for SSSD')
93
94     def get_config_obj(self):
95         return None
96
97     def enable(self):
98         self.refresh_plugin_config()
99         if not self.get_config_value('preconfigured'):
100             raise Exception("SSSD Can be enabled only if pre-configured")
101         super(InfoProvider, self).enable()
102
103
104 CONF_TEMPLATE = """
105 LoadModule lookup_identity_module modules/mod_lookup_identity.so
106
107 <Location /${instance}>
108   LookupUserAttr sn REMOTE_USER_LASTNAME
109   LookupUserAttr locality REMOTE_USER_STATE
110   LookupUserAttr street REMOTE_USER_STREET
111   LookupUserAttr telephoneNumber REMOTE_USER_TELEPHONENUMBER
112   LookupUserAttr givenname REMOTE_USER_FIRSTNAME
113   LookupUserAttr mail REMOTE_USER_EMAIL
114   LookupUserAttr postalCode REMOTE_USER_POSTALCODE
115   LookupUserGroupsIter REMOTE_USER_GROUP
116 </Location>
117 """
118
119
120 class Installer(InfoProviderInstaller):
121
122     def __init__(self, *pargs):
123         super(Installer, self).__init__()
124         self.name = 'sssd'
125         self.pargs = pargs
126
127     def install_args(self, group):
128         group.add_argument('--info-sssd', choices=['yes', 'no'],
129                            default='no',
130                            help='Use mod_lookup_identity and SSSD to populate'
131                                 ' user attrs')
132         group.add_argument('--info-sssd-domain', action='append',
133                            help='SSSD domain to enable mod_lookup_identity'
134                                 ' for')
135
136     def configure(self, opts):
137         if opts['info_sssd'] != 'yes':
138             return
139
140         configured = 0
141
142         confopts = {'instance': opts['instance']}
143
144         tmpl = Template(CONF_TEMPLATE)
145         hunk = tmpl.substitute(**confopts)
146         with open(opts['httpd_conf'], 'a') as httpd_conf:
147             httpd_conf.write(hunk)
148
149         try:
150             sssdconfig = SSSDConfig.SSSDConfig()
151             sssdconfig.import_config()
152         except Exception as e:  # pylint: disable=broad-except
153             # Unable to read existing SSSD config so it is probably not
154             # configured.
155             logging.info('Loading SSSD config failed: %s', e)
156             return False
157
158         if not opts['info_sssd_domain']:
159             domains = sssdconfig.list_domains()
160         else:
161             domains = opts['info_sssd_domain']
162
163         for domain in domains:
164             try:
165                 sssd_domain = sssdconfig.get_domain(domain)
166             except SSSDConfig.NoDomainError:
167                 logging.info('No SSSD domain %s', domain)
168                 continue
169             else:
170                 sssd_domain.set_option(
171                     'ldap_user_extra_attrs', ', '.join(SSSD_ATTRS)
172                 )
173                 sssdconfig.save_domain(sssd_domain)
174                 configured += 1
175                 logging.info("Configured SSSD domain %s", domain)
176
177         if configured == 0:
178             logging.info('No SSSD domains configured')
179             return False
180
181         try:
182             sssdconfig.new_service('ifp')
183         except SSSDConfig.ServiceAlreadyExists:
184             pass
185
186         sssdconfig.activate_service('ifp')
187
188         ifp = sssdconfig.get_service('ifp')
189         ifp.set_option('allowed_uids', 'apache, root')
190         ifp.set_option('user_attributes', '+' + ', +'.join(SSSD_ATTRS))
191
192         sssdconfig.save_service(ifp)
193         sssdconfig.write(SSSD_CONF)
194
195         # for selinux enabled platforms, ignore if it fails just report
196         try:
197             subprocess.call(['/usr/sbin/setsebool', '-P',
198                              'httpd_dbus_sssd=on'])
199         except Exception:  # pylint: disable=broad-except
200             pass
201
202         try:
203             subprocess.call(['/sbin/service', 'sssd', 'restart'])
204         except Exception:  # pylint: disable=broad-except
205             pass
206
207         # Give SSSD a chance to restart
208         time.sleep(5)
209
210         # Add configuration data to database
211         po = PluginObject(*self.pargs)
212         po.name = 'sssd'
213         po.wipe_data()
214         po.wipe_config_values()
215         config = {'preconfigured': True}
216         po.save_plugin_config(config)
217
218         # Update global config to add info plugin
219         po.is_enabled = True
220         po.save_enabled_state()