Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Values for a SAML Attribute #27

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion flask_saml2/sp/idphandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,9 @@ def validate_response(self, response: ResponseParser):
# Validate the AudienceRestriction elements, if they exist
audiences = response._xpath(response.conditions, './saml:AudienceRestriction/saml:Audience')
entity_id = self.sp.get_sp_entity_id()
adnc = [el.text for el in audiences]
if len(audiences) and not any(el.text == entity_id for el in audiences):
raise CannotHandleAssertion("No valid AudienceRestriction found")
raise CannotHandleAssertion(f"No valid AudienceRestriction found: {adnc}, {entity_id}")

def format_datetime(self, value: datetime.datetime) -> str:
"""
Expand Down
18 changes: 14 additions & 4 deletions flask_saml2/sp/parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Mapping, Optional
from typing import List, Mapping, Optional, Union

from flask_saml2.types import XmlNode
from flask_saml2.utils import cached_property
Expand Down Expand Up @@ -51,10 +51,20 @@ def nameid_format(self) -> str:
return self._xpath(self.subject, 'saml:NameID/@Format')[0]

@cached_property
def attributes(self) -> Mapping[str, str]:
def attributes(self) -> Mapping[str, Union[str, List[str]]]:
attributes = self._xpath(self.assertion, 'saml:AttributeStatement/saml:Attribute')
return {el.get('Name'): self._xpath(el, 'saml:AttributeValue')[0].text
for el in attributes}
ret = {}
for el in attributes:
name = el.get('Name')
attrs = self._xpath(el, 'saml:AttributeValue')
if len(attrs) == 1:
ret[name] = attrs[0].text
else:
vals = []
for a in attrs:
vals.append(a.text)
ret[name] = vals
return ret

@cached_property
def conditions(self) -> Optional[XmlNode]:
Expand Down
10 changes: 8 additions & 2 deletions flask_saml2/sp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,21 @@ def post(self):
saml_request = request.form['SAMLResponse']
relay_state = request.form['RelayState']

errors = []

for handler in self.sp.get_idp_handlers():
try:
response = handler.get_response_parser(saml_request)
auth_data = handler.get_auth_data(response)
return self.sp.login_successful(auth_data, relay_state)
except CannotHandleAssertion:
continue
except CannotHandleAssertion as e:
errors.append(e)
except UserNotAuthorized:
return self.sp.render_template('flask_saml2_sp/user_not_authorized.html')
error_string = ""
for e in errors:
error_string = f"{error_string} - {e} <br>"
return f"Could not log in for various reasons, here is a list of errors encountered: <br> {error_string}"


class Metadata(SAML2View):
Expand Down