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