# HG changeset patch # User Florent Aide <florent.aide@gmail.com> # Date 1455123438 -3600 # Wed Feb 10 17:57:18 2016 +0100 # Node ID e0507277b57de36f77d8d95f6f88ae8fa935093b # Parent 9b47f2baccaede3bbd6c3d4fd314e714221d7de0 implement real attribute matching instead of using subject.nameId.content for everything diff --git a/model/auth_saml.py b/model/auth_saml.py --- a/model/auth_saml.py +++ b/model/auth_saml.py @@ -29,6 +29,15 @@ ) return lasso.Login(server) + def _get_matching_attr_for_provider( + self, cr, uid, provider_id, context=None + ): + """internal helper to fetch the matching attribute for this SAML + provider. Returns a unicode object. + """ + provider = self.browse(cr, uid, provider_id, context=context) + return provider.matching_attribute + def _get_auth_request(self, cr, uid, id_, state, context=None): """build an authentication request and give it back to our client WARNING: this method cannot be used for multiple ids @@ -55,6 +64,7 @@ 'sp_pkey': fields.text( 'Private key of our service provider (this openerpserver)' ), + 'matching_attribute': fields.text('Matching Attribute', required=True), 'enabled': fields.boolean('Enabled'), 'css_class': fields.char('CSS class'), 'body': fields.char( @@ -66,6 +76,7 @@ _defaults = { 'enabled': False, + 'matching_attribute': "subject.nameId", 'css_class': 'zocial saml', 'body': 'Authentic', } diff --git a/model/res_users.py b/model/res_users.py --- a/model/res_users.py +++ b/model/res_users.py @@ -63,29 +63,81 @@ p = self.pool.get('auth.saml.provider') login = p._get_lasso_for_provider(cr, uid, provider, context=context) + matching_attribute = p._get_matching_attr_for_provider(cr, uid, provider, context=context) try: login.processAuthnResponseMsg(token) except (lasso.DsError, lasso.ProfileCannotVerifySignatureError): raise Exception('Lasso Profile cannot verify signature') - except lasso.Error, e: + except lasso.Error as e: raise Exception(repr(e)) try: login.acceptSso() - except lasso.Error: - raise Exception('Invalid assertion') + except lasso.Error as error: + raise Exception( + 'Invalid assertion : %s' % lasso.strError(error[0]) + ) - # TODO use a real token validation from LASSO - # TODO push into the validation result a real UPN - validation = {'user_id': login.assertion.subject.nameId.content} + attrs = {} - """ - if p.data_endpoint: - data = self._auth_oauth_rpc(cr, uid, p.data_endpoint, access_token) - validation.update(data) - """ + for att_statement in login.assertion.attributeStatement: + for attribute in att_statement.attribute: + name = None + lformat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC + nickname = None + try: + name = attribute.name.decode('ascii') + except Exception as e: + _logger.warning('sso_after_response: error decoding name of \ + attribute %s' % attribute.dump()) + else: + try: + if attribute.nameFormat: + lformat = attribute.nameFormat.decode('ascii') + if attribute.friendlyName: + nickname = attribute.friendlyName + except Exception as e: + message = 'sso_after_response: name or format of an \ + attribute failed to decode as ascii: %s due to %s' + _logger.warning(message % (attribute.dump(), str(e))) + try: + if name: + if lformat: + if nickname: + key = (name, lformat, nickname) + else: + key = (name, lformat) + else: + key = name + attrs[key] = list() + for value in attribute.attributeValue: + content = [a.exportToXml() for a in value.any] + content = ''.join(content) + attrs[key].append(content.decode('utf8')) + except Exception as e: + message = 'sso_after_response: value of an \ + attribute failed to decode as ascii: %s due to %s' + _logger.warning(message % (attribute.dump(), str(e))) + matching_value = None + for k in attrs: + if isinstance(k, tuple) and k[0] == matching_attribute: + matching_value = attrs[k][0] + break + + if not matching_value and matching_attribute == "subject.nameId": + matching_value = login.assertion.subject.nameId.content + + elif not matching_value and matching_attribute != "subject.nameId": + raise Exception( + "Matching attribute %s not found in user attrs: %s" % ( + matching_attribute, + attrs, + ) + ) + + validation = {'user_id': matching_value} return validation def _auth_saml_signin( diff --git a/views/auth_saml.xml b/views/auth_saml.xml --- a/views/auth_saml.xml +++ b/views/auth_saml.xml @@ -25,6 +25,7 @@ <field name="name" /> <field name="enabled" /> <field name="body" /> + <field name="matching_attribute" /> </group> <group> <field name="idp_metadata" />