Skip to content

Commit c56a1e0

Browse files
authored
Merge pull request #2 from IdentityPython/master
merge
2 parents 68fc62f + 79abc46 commit c56a1e0

File tree

5 files changed

+229
-47
lines changed

5 files changed

+229
-47
lines changed

example/plugins/frontends/saml2_virtualcofrontend.yaml.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ config:
2424
- contact_type: technical
2525
email_address: [email protected]
2626
given_name MESS Technical Support
27+
# SAML attributes and static values about the CO to be asserted for each user.
28+
# The key is the SATOSA internal attribute name.
29+
co_static_saml_attributes:
30+
organization: Medium Engergy Synchrotron Source
31+
countryname: US
32+
friendlycountryname: United States
33+
noreduorgacronym:
34+
- MESS
35+
- MeSyncS
2736
- encodeable_name: MTS
2837
organization:
2938
display_name: Milwaukee Theological Seminary

src/satosa/backends/saml2.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class SAMLBackend(BackendModule, SAMLBaseModule):
4040
A saml2 backend module (acting as a SP).
4141
"""
4242
KEY_DISCO_SRV = 'disco_srv'
43+
KEY_SAML_DISCOVERY_SERVICE_URL = 'saml_discovery_service_url'
44+
KEY_SAML_DISCOVERY_SERVICE_POLICY = 'saml_discovery_service_policy'
4345
KEY_SP_CONFIG = 'sp_config'
4446
VALUE_ACR_COMPARISON_DEFAULT = 'exact'
4547

@@ -102,9 +104,9 @@ def start_auth(self, context, internal_req):
102104
entity_id = idps[0]
103105
return self.authn_request(context, entity_id)
104106

105-
return self.disco_query()
107+
return self.disco_query(context)
106108

107-
def disco_query(self):
109+
def disco_query(self, context):
108110
"""
109111
Makes a request to the discovery server
110112
@@ -116,8 +118,21 @@ def disco_query(self):
116118
:param internal_req: The request
117119
:return: Response
118120
"""
119-
return_url = self.sp.config.getattr("endpoints", "sp")["discovery_response"][0][0]
120-
loc = self.sp.create_discovery_service_request(self.discosrv, self.sp.config.entityid, **{"return": return_url})
121+
endpoints = self.sp.config.getattr("endpoints", "sp")
122+
return_url = endpoints["discovery_response"][0][0]
123+
124+
disco_url = (
125+
context.get_decoration(self.KEY_SAML_DISCOVERY_SERVICE_URL) or self.discosrv
126+
)
127+
disco_policy = context.get_decoration(self.KEY_SAML_DISCOVERY_SERVICE_POLICY)
128+
129+
args = {"return": return_url}
130+
if disco_policy:
131+
args["policy"] = disco_policy
132+
133+
loc = self.sp.create_discovery_service_request(
134+
disco_url, self.sp.config.entityid, **args
135+
)
121136
return SeeOther(loc)
122137

123138
def construct_requested_authn_context(self, entity_id):

src/satosa/frontends/saml2.py

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ class SAMLVirtualCoFrontend(SAMLFrontend):
697697
"""
698698
KEY_CO = 'collaborative_organizations'
699699
KEY_CO_NAME = 'co_name'
700+
KEY_CO_ATTRIBUTES = 'co_static_saml_attributes'
700701
KEY_CONTACT_PERSON = 'contact_person'
701702
KEY_ENCODEABLE_NAME = 'encodeable_name'
702703
KEY_ORGANIZATION = 'organization'
@@ -726,11 +727,33 @@ def handle_authn_response(self, context, internal_response):
726727
:return:
727728
"""
728729

730+
return self._handle_authn_response(context, internal_response)
731+
732+
def _handle_authn_response(self, context, internal_response):
733+
"""
734+
"""
729735
# Using the context of the current request and saved state from the
730-
# authentication request dynamically create an IdP instance and then
731-
# use it to handle the authentication response.
736+
# authentication request dynamically create an IdP instance.
732737
idp = self._create_co_virtual_idp(context)
733-
return self._handle_authn_response(context, internal_response, idp)
738+
739+
# Add any static attributes for the CO.
740+
co_config = self._get_co_config(context)
741+
742+
if self.KEY_CO_ATTRIBUTES in co_config:
743+
attributes = internal_response.attributes
744+
for attribute, value in co_config[self.KEY_CO_ATTRIBUTES].items():
745+
# XXX This should be refactored when Python 3.4 support is
746+
# XXX no longer required to use isinstance(value, Iterable).
747+
try:
748+
if iter(value) and not isinstance(value, str):
749+
attributes[attribute] = value
750+
else:
751+
attributes[attribute] = [value]
752+
except TypeError:
753+
attributes[attribute] = [value]
754+
755+
# Handle the authentication response.
756+
return super()._handle_authn_response(context, internal_response, idp)
734757

735758
def _create_state_data(self, context, resp_args, relay_state):
736759
"""
@@ -747,6 +770,22 @@ def _create_state_data(self, context, resp_args, relay_state):
747770

748771
return state
749772

773+
def _get_co_config(self, context):
774+
"""
775+
Obtain the configuration for the CO.
776+
777+
:type context: The current context
778+
:rtype: dict
779+
780+
:param context: The current context
781+
:return: CO configuration
782+
783+
"""
784+
co_name = self._get_co_name(context)
785+
for co in self.config[self.KEY_CO]:
786+
if co[self.KEY_ENCODEABLE_NAME] == co_name:
787+
return co
788+
750789
def _get_co_name_from_path(self, context):
751790
"""
752791
The CO name is URL encoded and obtained from the request path
@@ -866,21 +905,19 @@ def _overlay_for_saml_metadata(self, config, co_name):
866905
867906
:return: config with updated details for SAML metadata
868907
"""
869-
for co in self.config[self.KEY_CO]:
870-
if co[self.KEY_ENCODEABLE_NAME] == co_name:
871-
break
908+
co_config = self._get_co_config(co_name)
872909

873910
key = self.KEY_ORGANIZATION
874-
if key in co:
911+
if key in co_config:
875912
if key not in config:
876913
config[key] = {}
877914
for org_key in self.KEY_ORGANIZATION_KEYS:
878-
if org_key in co[key]:
879-
config[key][org_key] = co[key][org_key]
915+
if org_key in co_config[key]:
916+
config[key][org_key] = co_config[key][org_key]
880917

881918
key = self.KEY_CONTACT_PERSON
882-
if key in co:
883-
config[key] = co[key]
919+
if key in co_config:
920+
config[key] = co_config[key]
884921

885922
return config
886923

tests/satosa/backends/test_saml2.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ def assert_redirect_to_idp(self, redirect_response, idp_conf):
4747
assert redirect_location == idp_conf["service"]["idp"]["endpoints"]["single_sign_on_service"][0][0]
4848
assert "SAMLRequest" in parse_qs(parsed.query)
4949

50-
def assert_redirect_to_discovery_server(self, redirect_response, sp_conf):
50+
def assert_redirect_to_discovery_server(
51+
self, redirect_response, sp_conf, expected_discosrv_url
52+
):
5153
assert redirect_response.status == "303 See Other"
5254
parsed = urlparse(redirect_response.message)
5355
redirect_location = "{parsed.scheme}://{parsed.netloc}{parsed.path}".format(parsed=parsed)
54-
assert redirect_location == DISCOSRV_URL
56+
assert redirect_location == expected_discosrv_url
5557

5658
request_params = dict(parse_qsl(parsed.query))
5759
assert request_params["return"] == sp_conf["service"]["sp"]["endpoints"]["discovery_response"][0][0]
@@ -99,7 +101,15 @@ def get_path_from_url(url):
99101

100102
def test_start_auth_defaults_to_redirecting_to_discovery_server(self, context, sp_conf):
101103
resp = self.samlbackend.start_auth(context, InternalData())
102-
self.assert_redirect_to_discovery_server(resp, sp_conf)
104+
self.assert_redirect_to_discovery_server(resp, sp_conf, DISCOSRV_URL)
105+
106+
def test_discovery_server_set_in_context(self, context, sp_conf):
107+
discosrv_url = 'https://my.org/saml_discovery_service'
108+
context.decorate(
109+
SAMLBackend.KEY_SAML_DISCOVERY_SERVICE_URL, discosrv_url
110+
)
111+
resp = self.samlbackend.start_auth(context, InternalData())
112+
self.assert_redirect_to_discovery_server(resp, sp_conf, discosrv_url)
103113

104114
def test_full_flow(self, context, idp_conf, sp_conf):
105115
test_state_key = "test_state_key_456afgrh"
@@ -110,7 +120,7 @@ def test_full_flow(self, context, idp_conf, sp_conf):
110120

111121
# start auth flow (redirecting to discovery server)
112122
resp = self.samlbackend.start_auth(context, InternalData())
113-
self.assert_redirect_to_discovery_server(resp, sp_conf)
123+
self.assert_redirect_to_discovery_server(resp, sp_conf, DISCOSRV_URL)
114124

115125
# fake response from discovery server
116126
disco_resp = parse_qs(urlparse(resp.message).query)
@@ -166,7 +176,7 @@ def test_always_redirect_to_discovery_service_if_using_mdq(self, context, sp_con
166176
samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf, "disco_srv": DISCOSRV_URL,},
167177
"base_url", "saml_backend")
168178
resp = samlbackend.start_auth(context, InternalData())
169-
self.assert_redirect_to_discovery_server(resp, sp_conf)
179+
self.assert_redirect_to_discovery_server(resp, sp_conf, DISCOSRV_URL)
170180

171181
def test_authn_request(self, context, idp_conf):
172182
resp = self.samlbackend.authn_request(context, idp_conf["entityid"])

0 commit comments

Comments
 (0)