Make SSSD Info enable the httpd_dbus_sssd boolean.
[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='store',
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         if not opts['info_sssd_domain']:
121             print 'info-identity-domain is required'
122             return False
123
124         confopts = {'instance': opts['instance']}
125
126         tmpl = Template(CONF_TEMPLATE)
127         hunk = tmpl.substitute(**confopts)  # pylint: disable=star-args
128         with open(opts['httpd_conf'], 'a') as httpd_conf:
129             httpd_conf.write(hunk)
130
131         try:
132             sssdconfig = SSSDConfig.SSSDConfig()
133             sssdconfig.import_config()
134         except Exception as e:  # pylint: disable=broad-except
135             # Unable to read existing SSSD config so it is probably not
136             # configured.
137             print 'Loading SSSD config failed: %s' % e
138             return False
139
140         try:
141             domain = sssdconfig.get_domain(opts['info_sssd_domain'])
142         except SSSDConfig.NoDomainError:
143             print 'No domain %s' % opts['info_sssd_domain']
144             return False
145
146         domain.set_option('ldap_user_extra_attrs', ', '.join(SSSD_ATTRS))
147
148         try:
149             sssdconfig.new_service('ifp')
150         except SSSDConfig.ServiceAlreadyExists:
151             pass
152
153         sssdconfig.activate_service('ifp')
154
155         ifp = sssdconfig.get_service('ifp')
156         ifp.set_option('allowed_uids', 'apache, root')
157         ifp.set_option('user_attributes', '+' + ', +'.join(SSSD_ATTRS))
158
159         sssdconfig.save_service(ifp)
160         sssdconfig.save_domain(domain)
161         sssdconfig.write(SSSD_CONF)
162
163         # for selinux enabled platforms, ignore if it fails just report
164         try:
165             subprocess.call(['/usr/sbin/setsebool', '-P',
166                              'httpd_dbus_sssd=on'])
167         except Exception:  # pylint: disable=broad-except
168             pass
169
170         try:
171             subprocess.call(['/sbin/service', 'sssd', 'restart'])
172         except Exception:  # pylint: disable=broad-except
173             pass
174
175         # Give SSSD a chance to restart
176         time.sleep(5)
177
178         # Add configuration data to database
179         po = PluginObject(*self.pargs)
180         po.name = 'sssd'
181         po.wipe_data()
182         po.wipe_config_values()
183
184         # Update global config to add info plugin
185         po.is_enabled = True
186         po.save_enabled_state()