Add info plugin that utilizes Apache mod_lookup_identity plugin
authorRob Crittenden <rcritten@redhat.com>
Thu, 12 Feb 2015 16:49:20 +0000 (11:49 -0500)
committerSimo Sorce <simo@redhat.com>
Fri, 13 Feb 2015 23:10:51 +0000 (18:10 -0500)
mod_look_identity looks up identity information from sssd over
dbus, making additional identity attributes available.

https://fedorahosted.org/ipsilon/ticket/31

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-by: Simo Sorce <simo@redhat.com>
contrib/fedora/ipsilon.spec
ipsilon/info/infosssd.py [new file with mode: 0644]

index 4b17aa7..3e0343b 100644 (file)
@@ -2,7 +2,7 @@
 
 Name:       ipsilon
 Version:    0.3.0
-Release:    5%{?dist}
+Release:    6%{?dist}
 Summary:    An Identity Provider Server
 
 Group:      System Environment/Base
@@ -159,6 +159,19 @@ BuildArch:      noarch
 %description authldap
 Provides a login plugin to allow authentication and info retrieval via LDAP.
 
+%package infosssd
+Summary:        SSSD & mod_lookup_identity-based identity plugin
+Group:          System Environment/Base
+License:        GPLv3+
+Requires:       %{name} = %{version}-%{release}
+Requires:       mod_lookup_identity
+Requires:       libsss_simpleifp
+Requires;       sssd >= 1.12.4
+BuildArch:      noarch
+
+%description infosssd
+Provides an info plugin to allow retrieval via mod_lookup_identity and
+SSSD.
 
 %prep
 %setup -q
@@ -287,8 +300,13 @@ fi
 %{python2_sitelib}/ipsilon/login/authldap*
 %{python2_sitelib}/ipsilon/info/infoldap*
 
+%files infosssd
+%{python2_sitelib}/ipsilon/info/infosssd.*
 
 %changelog
+* Thu Feb 12 2015 Rob Crittenden <rcritten@redhat.com> - 0.3.0-6
+- Add mod_identity_lookup info plugin package
+
 * Wed Jan 28 2015 Patrick Uiterwijk <puiterwijk@redhat.com> - 0.3.0-5
 - Split IPA tools
 
diff --git a/ipsilon/info/infosssd.py b/ipsilon/info/infosssd.py
new file mode 100644 (file)
index 0000000..b187567
--- /dev/null
@@ -0,0 +1,180 @@
+# Copyright (C) 2014 Ipsilon Project Contributors
+#
+# See the file named COPYING for the project license
+
+# Info plugin for mod_lookup_identity Apache module via SSSD
+# http://www.adelton.com/apache/mod_lookup_identity/
+
+from ipsilon.info.common import InfoProviderBase
+from ipsilon.info.common import InfoProviderInstaller
+from ipsilon.info.common import InfoMapping
+from ipsilon.util.plugin import PluginObject
+from string import Template
+import cherrypy
+import time
+import subprocess
+import SSSDConfig
+
+SSSD_CONF = '/etc/sssd/sssd.conf'
+
+# LDAP attributes to tell SSSD to fetch over the InfoPipe
+SSSD_ATTRS = ['mail',
+              'street',
+              'locality',
+              'postalCode',
+              'telephoneNumber',
+              'givenname',
+              'sn']
+
+# Map the mod_lookup_identity env variables to Ipsilon. The inverse of
+# this is in the httpd template.
+sssd_mapping = {
+    'REMOTE_USER_GECOS': 'fullname',
+    'REMOTE_USER_EMAIL': 'email',
+    'REMOTE_USER_FIRSTNAME': 'givenname',
+    'REMOTE_USER_LASTNAME': 'surname',
+    'REMOTE_USER_STREET': 'street',
+    'REMOTE_USER_STATE': 'state',
+    'REMOTE_USER_POSTALCODE': 'postcode',
+    'REMOTE_USER_TELEPHONENUMBER': 'phone',
+}
+
+
+class InfoProvider(InfoProviderBase):
+
+    def __init__(self, *pargs):
+        super(InfoProvider, self).__init__(*pargs)
+        self.mapper = InfoMapping()
+        self.mapper.set_mapping(sssd_mapping)
+        self.name = 'sssd'
+        self.new_config(self.name)
+
+    def _get_user_data(self, user):
+        reply = dict()
+        groups = []
+        expectgroups = int(cherrypy.request.wsgi_environ.get(
+            'REMOTE_USER_GROUP_N', 0))
+        for key in cherrypy.request.wsgi_environ:
+            if key.startswith('REMOTE_USER_'):
+                if key == 'REMOTE_USER_GROUP_N':
+                    continue
+                if key.startswith('REMOTE_USER_GROUP_'):
+                    groups.append(cherrypy.request.wsgi_environ[key])
+                else:
+                    reply[key] = cherrypy.request.wsgi_environ[key]
+        if len(groups) != expectgroups:
+            self.error('Number of groups expected was not found. Expected'
+                       ' %d got %d' % (expectgroups, len(groups)))
+        return reply, groups
+
+    def get_user_attrs(self, user):
+        reply = dict()
+        try:
+            attrs, groups = self._get_user_data(user)
+            userattrs, extras = self.mapper.map_attrs(attrs)
+            reply['userdata'] = userattrs
+            reply['groups'] = groups
+            reply['extras'] = {'sssd': extras}
+
+        except KeyError:
+            pass
+
+        return reply
+
+
+CONF_TEMPLATE = """
+LoadModule lookup_identity_module modules/mod_lookup_identity.so
+
+<Location /${instance}>
+  LookupUserAttr sn REMOTE_USER_LASTNAME
+  LookupUserAttr locality REMOTE_USER_STATE
+  LookupUserAttr street REMOTE_USER_STREET
+  LookupUserAttr telephoneNumber REMOTE_USER_TELEPHONENUMBER
+  LookupUserAttr givenname REMOTE_USER_FIRSTNAME
+  LookupUserAttr mail REMOTE_USER_EMAIL
+  LookupUserAttr postalCode REMOTE_USER_POSTALCODE
+  LookupUserGroupsIter REMOTE_USER_GROUP
+</Location>
+"""
+
+
+class Installer(InfoProviderInstaller):
+
+    def __init__(self, *pargs):
+        super(Installer, self).__init__()
+        self.name = 'sssd'
+        self.pargs = pargs
+
+    def install_args(self, group):
+        group.add_argument('--info-sssd', choices=['yes', 'no'],
+                           default='no',
+                           help='Use mod_lookup_identity and SSSD to populate'
+                                ' user attrs')
+        group.add_argument('--info-sssd-domain', action='store',
+                           help='SSSD domain to enable mod_lookup_identity'
+                                ' for')
+
+    def configure(self, opts):
+        if opts['info_sssd'] != 'yes':
+            return
+
+        if not opts['info_sssd_domain']:
+            print 'info-identity-domain is required'
+            return False
+
+        confopts = {'instance': opts['instance']}
+
+        tmpl = Template(CONF_TEMPLATE)
+        hunk = tmpl.substitute(**confopts)  # pylint: disable=star-args
+        with open(opts['httpd_conf'], 'a') as httpd_conf:
+            httpd_conf.write(hunk)
+
+        try:
+            sssdconfig = SSSDConfig.SSSDConfig()
+            sssdconfig.import_config()
+        except Exception as e:  # pylint: disable=broad-except
+            # Unable to read existing SSSD config so it is probably not
+            # configured.
+            print 'Loading SSSD config failed: %s' % e
+            return False
+
+        try:
+            domain = sssdconfig.get_domain(opts['info_sssd_domain'])
+        except SSSDConfig.NoDomainError:
+            print 'No domain %s' % opts['info_sssd_domain']
+            return False
+
+        domain.set_option('ldap_user_extra_attrs', ', '.join(SSSD_ATTRS))
+
+        try:
+            sssdconfig.new_service('ifp')
+        except SSSDConfig.ServiceAlreadyExists:
+            pass
+
+        sssdconfig.activate_service('ifp')
+
+        ifp = sssdconfig.get_service('ifp')
+        ifp.set_option('allowed_uids', 'apache, root')
+        ifp.set_option('user_attributes', '+' + ', +'.join(SSSD_ATTRS))
+
+        sssdconfig.save_service(ifp)
+        sssdconfig.save_domain(domain)
+        sssdconfig.write(SSSD_CONF)
+
+        try:
+            subprocess.call(['/sbin/service', 'sssd', 'restart'])
+        except Exception:  # pylint: disable=broad-except
+            pass
+
+        # Give SSSD a chance to restart
+        time.sleep(5)
+
+        # Add configuration data to database
+        po = PluginObject(*self.pargs)
+        po.name = 'sssd'
+        po.wipe_data()
+        po.wipe_config_values()
+
+        # Update global config to add info plugin
+        po.is_enabled = True
+        po.save_enabled_state()