IdentityPython / pysaml2

Python implementation of SAML2
Apache License 2.0
555 stars 422 forks source link

AttributeStatement missing from final response #925

Closed pwna5aurus closed 11 months ago

pwna5aurus commented 1 year ago

So I have an IdP built on Flask that handles SAML authn requests. However, the create_authn_response statement does not create any AttributeStatement node in the assertions, even though a well-formed identity dict is provided via the kwargs. I have tried changing the SP metadata to isRequired="true" for the attributes that are, again, mapped in the identity dict, but it still does not result in the inclusion of any AttributeStatement. In my id_conf.py, I specify: "attribute_restrictions": None. Everything else (aside from the AttributeStatement) seems to work.

Here's some of the debug output:

[DEBUG] [saml2.assertion.__init__] policy restrictions: {'default': {'lifetime': {'minutes': 15}, 'attribute_restrictions': None, 'name_form': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', 'entity_categories': None}}
[2023-08-06 19:21:32,093] [DEBUG] [saml2.assertion.filter] required: [
{'__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&RequestedAttribute', 'name': 'email', 'name_format': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'friendly_name': 'Email address', 'is_required': 'true'}, 
{'__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&RequestedAttribute', 'name': 'name', 'name_format': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'friendly_name': 'Full name', 'is_required': 'true'}, 
{'__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&RequestedAttribute', 'name': 'first_name', 'name_format': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'friendly_name': 'Given name', 'is_required': 'true'}, 
{'__class__': 'urn:oasis:names:tc:SAML:2.0:metadata&RequestedAttribute', 'name': 'last_name', 'name_format': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'friendly_name': 'Family name', 'is_required': 'true'}
], optional: None

Code Version

Version: 7.4.2

Expected Behavior

The code should emit a signed SAML response with signed assertions, including an AttributeStatement node.

Current Behavior

No AttributeStatement node in the signed SAML response.

Possible Solution

Unknown

Steps to Reproduce

Example:

identity = {
    'email': ['test@test.net'],
    'name': ['Test User'],
    'first_name': ['Test'],
    'last_name': ['User']
    }

    authn_response = idp.create_authn_response(
        identity=identity,
        userid='test@test.net',
        in_response_to=req.message.id,
        destination=req.message.assertion_consumer_service_url,
        sp_entity_id=req.message.issuer.text,
        authn={"class_ref": "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified",
               'authn_instant':authn_instant,  
               'session_not_on_or_after':session_not_on_or_after, 
               'session_index':f'{str(uuid.uuid4())}::{str(uuid.uuid4())}',
               'authn_context':authn_context
               },
        sign_assertion=True,
        sign_response=True
    )

    saml_response = idp.apply_binding(
        BINDING_HTTP_REDIRECT,
        authn_response,
        req.message.assertion_consumer_service_url,
        response=True
    )

(Here's a snippet from the SP Metadata for additional context)

<md:ServiceName xml:lang="en">Required attributes</md:ServiceName>
<md:RequestedAttribute FriendlyName="Email address" Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
<md:RequestedAttribute FriendlyName="Full name" Name="name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
<md:RequestedAttribute FriendlyName="Given name" Name="first_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
<md:RequestedAttribute FriendlyName="Family name" Name="last_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
pwna5aurus commented 1 year ago

@c00kiemon5ter ? can you shed any light on this?

c00kiemon5ter commented 1 year ago

By looking at this, I can only guess that the attributes are filtered out because of their name format and name not matching a predefined attribute map.

Please, try with attributes that are recognized.

We should probably add a debug logline after the filtering to visualise its result.

pwna5aurus commented 1 year ago

Actually, the problem was ultimately this line in Construct():

https://github.com/IdentityPython/pysaml2/blob/master/src/saml2/assertion.py#L834 _ass.attribute_statement = [attr_statement]

Which I changed to _ass.attribute_statement.append(attr_statement) in my local copy, and it works just fine. (I believe I made some other changes locally, like hardcoding the attr-format, because I was losing my mind over this 😂)