Skip to content

Commit 52f0daa

Browse files
committed
Close #218. Add support for get_friendlyname_attributes and get_friendlyname_attribute
1 parent f1e4af2 commit 52f0daa

File tree

5 files changed

+88
-1
lines changed

5 files changed

+88
-1
lines changed

src/onelogin/saml2/auth.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
5353
else:
5454
self.__settings = OneLogin_Saml2_Settings(old_settings, custom_base_path)
5555
self.__attributes = dict()
56+
self.__friendlyname_attributes = dict()
5657
self.__nameid = None
5758
self.__nameid_format = None
5859
self.__nameid_nq = None
@@ -107,6 +108,7 @@ def process_response(self, request_id=None):
107108

108109
if response.is_valid(self.__request_data, request_id):
109110
self.__attributes = response.get_attributes()
111+
self.__friendlyname_attributes = response.get_friendlyname_attributes()
110112
self.__nameid = response.get_nameid()
111113
self.__nameid_format = response.get_nameid_format()
112114
self.__nameid_nq = response.get_nameid_nq()
@@ -231,6 +233,15 @@ def get_attributes(self):
231233
"""
232234
return self.__attributes
233235

236+
def get_friendlyname_attributes(self):
237+
"""
238+
Returns the set of SAML attributes indexed by FiendlyName.
239+
240+
:returns: SAML attributes
241+
:rtype: dict
242+
"""
243+
return self.__friendlyname_attributes
244+
234245
def get_nameid(self):
235246
"""
236247
Returns the nameID.
@@ -321,6 +332,19 @@ def get_attribute(self, name):
321332
assert isinstance(name, compat.str_type)
322333
return self.__attributes.get(name)
323334

335+
def get_friendlyname_attribute(self, friendlyname):
336+
"""
337+
Returns the requested SAML attribute searched by FriendlyName.
338+
339+
:param friendlyname: FriendlyName of the attribute
340+
:type friendlyname: string
341+
342+
:returns: Attribute value(s) if exists or None
343+
:rtype: list
344+
"""
345+
assert isinstance(friendlyname, compat.str_type)
346+
return self.__friendlyname_attributes.get(friendlyname)
347+
324348
def get_last_request_id(self):
325349
"""
326350
:returns: The ID of the last Request SAML message generated.

src/onelogin/saml2/response.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,42 @@ def get_attributes(self):
603603
attributes[attr_name] = values
604604
return attributes
605605

606+
def get_friendlyname_attributes(self):
607+
"""
608+
Gets the Attributes from the AttributeStatement element indexed by FiendlyName.
609+
EncryptedAttributes are not supported
610+
"""
611+
attributes = {}
612+
attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
613+
for attribute_node in attribute_nodes:
614+
attr_friendlyname = attribute_node.get('FriendlyName')
615+
if attr_friendlyname:
616+
if attr_friendlyname in attributes.keys():
617+
raise OneLogin_Saml2_ValidationError(
618+
'Found an Attribute element with duplicated FriendlyName',
619+
OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
620+
)
621+
622+
values = []
623+
for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
624+
attr_text = OneLogin_Saml2_XML.element_text(attr)
625+
if attr_text:
626+
attr_text = attr_text.strip()
627+
if attr_text:
628+
values.append(attr_text)
629+
630+
# Parse any nested NameID children
631+
for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']):
632+
values.append({
633+
'NameID': {
634+
'Format': nameid.get('Format'),
635+
'NameQualifier': nameid.get('NameQualifier'),
636+
'value': nameid.text
637+
}
638+
})
639+
attributes[attr_friendlyname] = values
640+
return attributes
641+
606642
def validate_num_assertions(self):
607643
"""
608644
Verifies that the document only contains a single Assertion (encrypted or not)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeDhmZmIzOTgzLWNiZjYtOTJhMS1mMmM0LTYxOWFlMWJlMWM4NiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4OGZmYjM5ODMtY2JmNi05MmExLWYyYzQtNjE5YWUxYmUxYzg2Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5oZ3VRYkNIYW5pYmJEQzdxM1p6eHpIY1Bvbkk9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPkdhbmNEOXZSb2g5TWJUMDAyRHk3OXQxbTZJNllmaFVLUGZibGttcDJ1ZG9sWHVqdjZlMU1XdnNWbXhOenRzSUdseEFhMHFLRGlTTXpDTkRac2szanN5c1VsMW5BS25BZzE4NWpmWGpzemhzZG1SK005MWR4azZrZmNMVW9zT29sb3ZhZFdMUFdxbjdQM2o4LzV4enA5THBSQTNndkI0MTgyUlNpcldDQlhQUT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT4NCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgRnJpZW5kbHlOYW1lPSJ1c2VybmFtZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWU8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ def testProcessResponseValid(self):
240240
attributes = auth.get_attributes()
241241
self.assertNotEqual(len(attributes), 0)
242242
self.assertEqual(auth.get_attribute('mail'), attributes['mail'])
243+
friendlyname_attributes = auth.get_friendlyname_attributes()
244+
self.assertEqual(len(friendlyname_attributes), 0)
243245
session_index = auth.get_session_index()
244246
self.assertEqual('_6273d77b8cde0c333ec79d22a9fa0003b9fe2d75cb', session_index)
245247
self.assertEqual("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", auth.get_nameid_format())

tests/src/OneLogin/saml2_tests/response_test.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ def testGetSessionIndex(self):
656656

657657
def testGetAttributes(self):
658658
"""
659-
Tests the getAttributes method of the OneLogin_Saml2_Response
659+
Tests the get_attributes method of the OneLogin_Saml2_Response
660660
"""
661661
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
662662
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
@@ -678,6 +678,30 @@ def testGetAttributes(self):
678678
response_3 = OneLogin_Saml2_Response(settings, xml_3)
679679
self.assertEqual({}, response_3.get_attributes())
680680

681+
def testGetFriendlyAttributes(self):
682+
"""
683+
Tests the get_friendlyname_attributes method of the OneLogin_Saml2_Response
684+
"""
685+
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
686+
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
687+
response = OneLogin_Saml2_Response(settings, xml)
688+
self.assertEqual({}, response.get_friendlyname_attributes())
689+
690+
expected_attributes = {
691+
'username': ['demo']
692+
}
693+
xml_2 = self.file_contents(join(self.data_path, 'responses', 'response1_with_friendlyname.xml.base64'))
694+
response_2 = OneLogin_Saml2_Response(settings, xml_2)
695+
self.assertEqual(expected_attributes, response_2.get_friendlyname_attributes())
696+
697+
xml_3 = self.file_contents(join(self.data_path, 'responses', 'response2.xml.base64'))
698+
response_3 = OneLogin_Saml2_Response(settings, xml_3)
699+
self.assertEqual({}, response_3.get_friendlyname_attributes())
700+
701+
xml_4 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'encrypted_attrs.xml.base64'))
702+
response_4 = OneLogin_Saml2_Response(settings, xml_4)
703+
self.assertEqual({}, response_4.get_friendlyname_attributes())
704+
681705
def testGetNestedNameIDAttributes(self):
682706
"""
683707
Tests the getAttributes method of the OneLogin_Saml2_Response with nested

0 commit comments

Comments
 (0)