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

Config to disable discovery initiated SAMLBackend flows #322

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions example/plugins/backends/saml2_backend.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,11 @@ config:
# include a Format attribute in the NameIDPolicy.
# name_id_format: 'None'
name_id_format_allow_create: true

# disco_srv must be defined if there is more than one IdP in the metadata specified above
disco_srv: http://disco.example.com

# Allow flows to start at the discovery response endpoint.
# Not recommend but allowed by default for backwards compatiblity.
# Default will change to false in a later release.
# allow_discovery_initiated: true
23 changes: 21 additions & 2 deletions src/satosa/backends/saml2.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from satosa.context import Context
from satosa.internal import AuthenticationInformation
from satosa.internal import InternalData
from satosa.exception import SATOSAAuthenticationError
from satosa.exception import SATOSAAuthenticationError, SATOSAStateError
from satosa.response import SeeOther, Response
from satosa.saml_util import make_saml_response
from satosa.metadata_creation.description import (
Expand Down Expand Up @@ -78,7 +78,9 @@ class SAMLBackend(BackendModule, SAMLBaseModule):
"""
A saml2 backend module (acting as a SP).
"""
KEY_ALLOW_DISCO_INIT_CONFIG = 'allow_discovery_initiated'
KEY_DISCO_SRV = 'disco_srv'
KEY_SAML_DISCOVERY_INITIATED = 'saml_discovery_initiated'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skoranda could we adopt KEY_ALLOW_DISCO_INIT_CONFIG only, without KEY_SAML_DISCOVERY_INITIATED?

KEY_SAML_DISCOVERY_SERVICE_URL = 'saml_discovery_service_url'
KEY_SAML_DISCOVERY_SERVICE_POLICY = 'saml_discovery_service_policy'
KEY_SP_CONFIG = 'sp_config'
Expand Down Expand Up @@ -170,6 +172,7 @@ def start_auth(self, context, internal_req):
:type internal_req: satosa.internal.InternalData
:rtype: satosa.response.Response
"""
context.state[self.name] = {}

entity_id = self.get_idp_entity_id(context)
if entity_id is None:
Expand Down Expand Up @@ -212,6 +215,10 @@ def disco_query(self, context):
loc = self.sp.create_discovery_service_request(
disco_url, self.sp.config.entityid, **args
)

state_dict = context.state[self.name]
state_dict[SAMLBackend.KEY_SAML_DISCOVERY_INITIATED] = True

return SeeOther(loc)

def construct_requested_authn_context(self, entity_id):
Expand Down Expand Up @@ -368,12 +375,24 @@ def disco_response(self, context):
"""
info = context.request
state = context.state
session_id = lu.get_session_id(state)
state_dict = state.get(self.name, {})
disco_initiated = state_dict.get(
SAMLBackend.KEY_SAML_DISCOVERY_INITIATED,
False)

if (not self.config.get(SAMLBackend.KEY_ALLOW_DISCO_INIT_CONFIG, True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without KEY_SAML_DISCOVERY_INITIATED it would be

if not self.config.get(SAMLBackend.KEY_ALLOW_DISCO_INIT_CONFIG, False):
    # ....

and not disco_initiated):
msg = "IdP discovery initiated flow not allowed"
logline = lu.LOG_FMT.format(id=session_id, message=msg)
logger.debug(logline, exc_info=True)
raise SATOSAStateError(state, msg)

try:
entity_id = info["entityID"]
except KeyError as err:
msg = "No IDP chosen for state"
logline = lu.LOG_FMT.format(id=lu.get_session_id(state), message=msg)
logline = lu.LOG_FMT.format(id=session_id, message=msg)
logger.debug(logline, exc_info=True)
raise SATOSAAuthenticationError(state, "No IDP chosen") from err

Expand Down
55 changes: 55 additions & 0 deletions tests/satosa/backends/test_saml2.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from satosa.backends.saml2 import SAMLBackend
from satosa.context import Context
from satosa.exception import SATOSAStateError
from satosa.internal import InternalData
from tests.users import USERS
from tests.util import FakeIdP, create_metadata_from_config_dict, FakeSP
Expand Down Expand Up @@ -348,6 +349,60 @@ def test_get_metadata_desc_with_logo_without_lang(self, sp_conf, idp_conf):
assert ui_info["description"] == expected_ui_info["description"]
assert ui_info["logo"] == expected_ui_info["logo"]

def test_allow_discovery_initiated(self, sp_conf, context, idp_conf):

# Test that with allow_discovery_initiated set to True can initiate
# flow at disco_response() and be directed to the correct IdP.
config = {"sp_config": sp_conf,
"disco_srv": DISCOSRV_URL,
"allow_discovery_initiated": True}

samlbackend = SAMLBackend(
Mock(),
INTERNAL_ATTRIBUTES,
config,
"base_url",
"samlbackend",
)

context.request = {'entityID': idp_conf["entityid"]}
resp = samlbackend.disco_response(context)
assert_redirect_to_idp(resp, idp_conf)

# Test that with allow_discovery_initiated set to False can not
# initiate flow at disco_response() and instead raises exception.
config = {"sp_config": sp_conf,
"disco_srv": DISCOSRV_URL,
SAMLBackend.KEY_ALLOW_DISCO_INIT_CONFIG: False}

samlbackend = SAMLBackend(
Mock(),
INTERNAL_ATTRIBUTES,
config,
"base_url",
"samlbackend",
)

context.request = {'entityID': idp_conf["entityid"]}

with pytest.raises(SATOSAStateError):
resp = samlbackend.disco_response(context)

# Test that with allow_discovery_initiated set to False a flow
# that begins in the backend with start_auth() marks the state
# as having been initiated properly through the disco_query()
# method on the backend and that the flow succeeds in redirecting
# to the IdP selected during discovery.
samlbackend.start_auth(context, InternalData())
name = samlbackend.name
key = SAMLBackend.KEY_SAML_DISCOVERY_INITIATED
initiated = context.state[name][key]
assert initiated is True

context.request = {'entityID': idp_conf["entityid"]}
resp = samlbackend.disco_response(context)
assert_redirect_to_idp(resp, idp_conf)


class TestSAMLBackendRedirects:
def test_default_redirect_to_discovery_service_if_using_mdq(
Expand Down