diff options
-rwxr-xr-x | ipsilon/info/common.py | 42 | ||||
-rwxr-xr-x | ipsilon/info/infoldap.py | 54 | ||||
-rwxr-xr-x | ipsilon/info/nss.py | 55 | ||||
-rwxr-xr-x | ipsilon/login/authldap.py | 10 | ||||
-rwxr-xr-x | ipsilon/login/authtest.py | 3 | ||||
-rwxr-xr-x | ipsilon/login/common.py | 12 | ||||
-rwxr-xr-x | ipsilon/providers/openid/auth.py | 13 | ||||
-rwxr-xr-x | ipsilon/providers/openidp.py | 2 | ||||
-rwxr-xr-x | ipsilon/providers/saml2/auth.py | 37 | ||||
-rw-r--r-- | templates/openid/consent_form.html | 4 |
10 files changed, 195 insertions, 37 deletions
diff --git a/ipsilon/info/common.py b/ipsilon/info/common.py index c4be8fe..92a3ba2 100755 --- a/ipsilon/info/common.py +++ b/ipsilon/info/common.py @@ -47,6 +47,48 @@ class InfoProviderBase(PluginObject, Log): self.debug('Info plugin disabled: %s' % self.name) +class InfoMapping(Log): + + def __init__(self): + self.standard_attributes = { + 'fullname': 'Full Name', + 'nickname': 'Nickname', + 'surname': 'Last Name', + 'firstname': 'First Name', + 'title': 'Title', + 'dob': 'Date of Birth', + 'email': 'E-mail Address', + 'gender': 'Gender', + 'postcode': 'Postal Code', + 'street': 'Street Address', + 'state': 'State or Province', + 'country': 'Country', + 'phone': 'Telephone Number', + 'language': 'Language', + 'timezone': 'Time Zone', + } + self.mapping = dict() + + def set_mapping(self, attrs_map): + self.mapping = attrs_map + + def display_name(self, name): + if name in self.standard_attributes: + return self.standard_attributes[name] + return name + + def map_attrs(self, attrs): + s = dict() + e = dict() + for a in attrs: + if a in self.mapping: + s[self.mapping[a]] = attrs[a] + else: + e[a] = attrs[a] + + return s, e + + FACILITY = 'info_config' diff --git a/ipsilon/info/infoldap.py b/ipsilon/info/infoldap.py index 6d710bd..fb1c121 100755 --- a/ipsilon/info/infoldap.py +++ b/ipsilon/info/infoldap.py @@ -6,15 +6,33 @@ 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 ipsilon.util.log import Log import ldap +# TODO: fetch mapping from configuration +ldap_mapping = { + 'cn': 'fullname', + 'commonname': 'fullname', + 'sn': 'surname', + 'mail': 'email', + 'destinationindicator': 'country', + 'postalcode': 'postcode', + 'st': 'state', + 'statetorprovincename': 'state', + 'streetaddress': 'street', + 'telephonenumber': 'phone', +} + + class InfoProvider(InfoProviderBase, Log): def __init__(self): super(InfoProvider, self).__init__() + self.mapper = InfoMapping() + self.mapper.set_mapping(ldap_mapping) self.name = 'ldap' self.description = """ Info plugin that uses LDAP to retrieve user data. """ @@ -92,24 +110,48 @@ Info plugin that uses LDAP to retrieve user data. """ return conn - def get_user_data_from_conn(self, conn, dn): + def _get_user_data(self, conn, dn): result = conn.search_s(dn, ldap.SCOPE_BASE) if result is None or result == []: raise Exception('User object could not be found!') elif len(result) > 1: raise Exception('No unique user object could be found!') - return result[0][1] + data = dict() + for name, value in result[0][1].iteritems(): + if type(value) is list and len(value) == 1: + value = value[0] + data[name] = value + return data + + def _get_user_groups(self, conn, dn, ldapattrs): + # TODO: fixme to support RFC2307bis schemas + if 'memberuid' in ldapattrs: + return ldapattrs['memberuid'] + else: + return [] + + def get_user_data_from_conn(self, conn, dn): + reply = dict() + try: + ldapattrs = self._get_user_data(conn, dn) + userattrs, extras = self.mapper.map_attrs(ldapattrs) + groups = self._get_user_groups(conn, dn, ldapattrs) + reply['userdata'] = userattrs + reply['groups'] = groups + reply['extras'] = {'ldap': extras} + except Exception, e: # pylint: disable=broad-except + self.error(e) + + return reply def get_user_attrs(self, user): - userattrs = None try: conn = self._ldap_bind() dn = self.user_dn_tmpl % {'username': user} - userattrs = self.get_user_data_from_conn(conn, dn) + return self.get_user_data_from_conn(conn, dn) except Exception, e: # pylint: disable=broad-except self.error(e) - - return userattrs + return {} class Installer(InfoProviderInstaller): diff --git a/ipsilon/info/nss.py b/ipsilon/info/nss.py index e9a3a96..4208442 100755 --- a/ipsilon/info/nss.py +++ b/ipsilon/info/nss.py @@ -6,27 +6,70 @@ from ipsilon.info.common import InfoProviderBase from ipsilon.info.common import InfoProviderInstaller +from ipsilon.info.common import InfoMapping from ipsilon.util.plugin import PluginObject +import grp import pwd +import os + + +posix_map = { + 'gecos': 'fullname' +} class InfoProvider(InfoProviderBase): def __init__(self): super(InfoProvider, self).__init__() + self.mapper = InfoMapping() + self.mapper.set_mapping(posix_map) self.name = 'nss' + def _get_posix_user(self, user): + p = pwd.getpwnam(user) + return {'username': p.pw_name, 'uidNumber': p.pw_uid, + 'gidNumber': p.pw_gid, 'gecos': p.pw_gecos, + 'homeDirectory': p.pw_dir, 'loginShell': p.pw_shell} + + def _get_posix_groups(self, user, group): + groups = set() + getgrouplist = getattr(os, 'getgrouplist', None) + if getgrouplist: + ids = getgrouplist(user, group) + for i in ids: + try: + g = grp.getgrgid(i) + groups.add(g.gr_name) + except KeyError: + pass + + else: + g = grp.getgrgid(group) + groups.add(g.gr_name) + + allg = grp.getgrall() + for g in allg: + if user in g.gr_mem: + groups.add(g.gr_name) + + return list(groups) + def get_user_attrs(self, user): - userattrs = None + reply = dict() try: - p = pwd.getpwnam(user) - userattrs = {'uidNumber': p[2], 'gidNumber': p[3], - 'gecos': p[4], 'homeDirectory': p[5], - 'loginShell': p[6]} + posix_user = self._get_posix_user(user) + userattrs, extras = self.mapper.map_attrs(posix_user) + groups = self._get_posix_groups(posix_user['username'], + posix_user['gidNumber']) + reply['userdata'] = userattrs + reply['groups'] = groups + reply['extras'] = {'posix': extras} + except KeyError: pass - return userattrs + return reply class Installer(InfoProviderInstaller): diff --git a/ipsilon/login/authldap.py b/ipsilon/login/authldap.py index 0d70479..a41d167 100755 --- a/ipsilon/login/authldap.py +++ b/ipsilon/login/authldap.py @@ -64,7 +64,15 @@ class LDAP(LoginFormBase, Log): if username and password: try: - userattrs = self._authenticate(username, password) + userdata = self._authenticate(username, password) + if userdata: + userattrs = dict() + for d, v in userdata.get('userdata', {}).items(): + userattrs[d] = v + if 'groups' in userdata: + userattrs['groups'] = userdata['groups'] + if 'extras' in userdata: + userattrs['extras'] = userdata['extras'] authed = True except Exception, e: # pylint: disable=broad-except errmsg = "Authentication failed" diff --git a/ipsilon/login/authtest.py b/ipsilon/login/authtest.py index 55b30a4..44492a4 100755 --- a/ipsilon/login/authtest.py +++ b/ipsilon/login/authtest.py @@ -33,8 +33,9 @@ class TestAuth(LoginFormBase): if username and password: if password == 'ipsilon': cherrypy.log("User %s successfully authenticated." % username) + testdata = {'fullname': 'Test User %s' % username} return self.lm.auth_successful(self.trans, - username, 'password') + username, 'password', testdata) else: cherrypy.log("User %s failed authentication." % username) error = "Authentication failed" diff --git a/ipsilon/login/common.py b/ipsilon/login/common.py index 2fee357..6231997 100755 --- a/ipsilon/login/common.py +++ b/ipsilon/login/common.py @@ -49,9 +49,17 @@ class LoginManagerBase(PluginObject, Log): if self.info: userattrs = self.info.get_user_attrs(username) if userdata: - userdata.update(userattrs or {}) + userdata.update(userattrs.get('userdata', {})) else: - userdata = userattrs + userdata = userattrs.get('userdata', {}) + + # merge groups and extras from login plugin and info plugin + userdata['groups'] = list(set(userdata.get('groups', []) + + userattrs.get('groups', []))) + + userdata['extras'] = userdata.get('extras', {}) + userdata['extras'].update(userattrs.get('extras', {})) + self.debug("User %s attributes: %s" % (username, repr(userdata))) if auth_type: diff --git a/ipsilon/providers/openid/auth.py b/ipsilon/providers/openid/auth.py index abf19ae..868daf1 100755 --- a/ipsilon/providers/openid/auth.py +++ b/ipsilon/providers/openid/auth.py @@ -162,17 +162,16 @@ class AuthenticateRequest(ProviderPageBase): 'openid_request': json.dumps(kwargs)} self.trans.store(data) - # Add extension data to this list of dictionaries - ad = [ - { - "Trust Root": request.trust_root, - }, - ] + # Add extension data to this dictionary + ad = { + "Trust Root": request.trust_root, + } userattrs = us.get_user_attrs() for n, e in self.cfg.extensions.items(): data = e.get_display_data(request, userattrs) self.debug('%s returned %s' % (n, repr(data))) - ad.append(data) + for key, value in data.items(): + ad[self.cfg.mapping.display_name(key)] = value context = { "title": 'Consent', diff --git a/ipsilon/providers/openidp.py b/ipsilon/providers/openidp.py index 2e41050..a3e1b63 100755 --- a/ipsilon/providers/openidp.py +++ b/ipsilon/providers/openidp.py @@ -9,6 +9,7 @@ from ipsilon.providers.common import FACILITY from ipsilon.providers.openid.auth import OpenID from ipsilon.providers.openid.extensions.common import LoadExtensions from ipsilon.util.plugin import PluginObject +from ipsilon.info.common import InfoMapping from openid.server.server import Server # TODO: Move this to the database @@ -19,6 +20,7 @@ class IdpProvider(ProviderBase): def __init__(self): super(IdpProvider, self).__init__('openid', 'openid') + self.mapping = InfoMapping() self.page = None self.server = None self.basepath = None diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py index cbfeaaa..87f4ac8 100755 --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -210,18 +210,33 @@ class AuthenticateRequest(ProviderPageBase): if not attrstat.attribute: attrstat.attribute = () - attributes = us.get_user_attrs() + attributes = dict() + userattrs = us.get_user_attrs() + for key, value in userattrs.get('userdata', {}).iteritems(): + if type(value) is str: + attributes[key] = value + if 'groups' in userattrs: + attributes['group'] = userattrs['groups'] + for _, info in userattrs.get('extras', {}).iteritems(): + for key, value in info.items(): + attributes[key] = value + for key in attributes: - attr = lasso.Saml2Attribute() - attr.name = key - attr.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC - value = str(attributes[key]).encode('utf-8') - node = lasso.MiscTextNode.newWithString(value) - node.textChild = True - attrvalue = lasso.Saml2AttributeValue() - attrvalue.any = [node] - attr.attributeValue = [attrvalue] - attrstat.attribute = attrstat.attribute + (attr,) + values = attributes[key] + if type(values) is not list: + values = [values] + for value in values: + attr = lasso.Saml2Attribute() + attr.name = key + attr.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC + value = str(value).encode('utf-8') + self.debug('value %s' % value) + node = lasso.MiscTextNode.newWithString(value) + node.textChild = True + attrvalue = lasso.Saml2AttributeValue() + attrvalue.any = [node] + attr.attributeValue = [attrvalue] + attrstat.attribute = attrstat.attribute + (attr,) self.debug('Assertion: %s' % login.assertion.dump()) diff --git a/templates/openid/consent_form.html b/templates/openid/consent_form.html index 8c3813e..e8ead1d 100644 --- a/templates/openid/consent_form.html +++ b/templates/openid/consent_form.html @@ -11,13 +11,11 @@ <form class="form-horizontal" role="form" id="consent_form" action="{{ action }}" method="post" enctype="application/x-www-form-urlencoded"> <input type="hidden" name="ipsilon_transaction_id" id="ipsilon_transaction_id" value="{{ ipsilon_transaction_id }}"> <div class="alert alert-danger"> -{%- for items in authz_details %} - {%- for item in items|dictsort %} +{%- for item in authz_details|dictsort %} <div class="form-group"> <div class="col-sm-10 col-md-10">{{ item[0] }}:</div> <div class="col-sm-10 col-md-10">{{ item[1] }}</div> </div> - {%- endfor %} {%- endfor %} </div> <div class="form-group"> |