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

(WIP) enable microservice-based flow to replay AuthnRequest (requires wsgi_app in registered callback) #288

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
11 changes: 11 additions & 0 deletions example/plugins/microservices/custom_routing.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module: satosa.micro_services.custom_routing.DecideIfRequesterIsAllowed
name: RequesterDecider
config:
rules:
target_entity_id1:
allow: ["requester1", "requester2"]
target_entity_id2:
deny: ["requester3"]
target_entity_id3:
allow: ["requester1"]
deny: ["*"]
9 changes: 9 additions & 0 deletions example/plugins/microservices/filter_requester.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Requester whitelist: this microservice cannot be used for blacklisting
config:
allow:
# this list must not be empty. Use '*' to allow all requesters
- https://sp1.test.wpv.portalverbund.at/sp.xml
- https://sp3.test.wpv.portalverbund.at/sp.xml
- https://sp4.test.wpv.portalverbund.at/sp.xml
module: satosa.micro_services.filter_requester.FilterRequester
name: FilterRequester
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module: satosa.micro_services.redirect_url.RedirectUrlRequest
name: RedirectUrlRequest
config:
db_encryption_key: "WQpuhOELqGAs/ct2mujCqw"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module: satosa.micro_services.redirect_url.RedirectUrlResponse
name: RedirectUrlResponse
config:
db_encryption_key: "WQpuhOELqGAs/ct2mujCqw" # must be same in redirect_url_request.yaml
redirect_attr_name: WkisRedirect
redir_entityid: http://wkis.qss.wko.at/adfs/services/trust
36 changes: 36 additions & 0 deletions example/plugins/microservices/simpleconsent.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# this file contains secret keys. Protect file or insert values from environment

module: satosa.micro_services.simpleconsent.SimpleConsent
name: SimpleConsent
config:
consent_cookie_name: SATOSA_CONSENTID
consent_attr_not_displayed:
- addr_country
- authenticationClass
- possibleroles
- registrationClassOrg
- registrationClassUser
- wkis_roleextendeddescription
- wkis_roletypeid
consent_attrname_display:
uid: UserID
displayname: Anzeigename
givenname: Vorname
gln: "GLN der Firma"
mail: E-Mailadresse
name: Vor+Familienname
surname: Familienname
wkis_roledescription: "WKO Mitglied"
consent_service_api_auth:
userid: admin
password: adminadmin
id_hash_alg: md5 # md5 is shorter than sha-224, but may be missing in (rare) FIPS-compliant C-Python
# shard secret: configure same ASCII-value in proxy and consent app
PROXY_HMAC_KEY: "your random key (-> `openssh rand -base64 30`)"
request_consent_url: https://consent.example.org/request_consent
self_entityid: satosa.vnet/idp_proxy.xml
# consent display page: translate SP entityIDs to human readable names
# (work around, as metadata is not available in the micro service)
sp_entityid_names:
https://sp1.test.wpv.portalverbund.at/sp.xml: "Test SP1"
verify_consent_url: https://consent.example.org/has_consent
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"pystache"
],
extras_require={
"ldap": ["ldap3"]
"ldap": ["ldap3"],
"redirecturl": ["redis"],
},
zip_safe=False,
classifiers=[
Expand Down
2 changes: 1 addition & 1 deletion src/satosa/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, auth_callback_func, internal_attributes, base_url, name):
self.base_url = base_url
self.name = name

def start_auth(self, context, internal_request):
def start_auth(self, context, internal_request, **kwargs):
"""
This is the start up function of the backend authorization.
Expand Down
4 changes: 2 additions & 2 deletions src/satosa/backends/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
outgoing, internal_attributes, config, base_url, name, 'github',
'id')

def start_auth(self, context, internal_request, get_state=stateID):
def start_auth(self, context, internal_request, get_state=stateID, **kwargs):
"""
:param get_state: Generates a state to be used in authentication call
Expand Down Expand Up @@ -75,7 +75,7 @@ def auth_info(self, requrest):
UNSPECIFIED, None,
self.config['server_info']['authorization_endpoint'])

def _authn_response(self, context):
def _authn_response(self, context, **kwargs):
state_data = context.state[self.name]
aresp = self.consumer.parse_response(
AuthorizationResponse, info=json.dumps(context.request))
Expand Down
4 changes: 2 additions & 2 deletions src/satosa/backends/linkedin.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
outgoing, internal_attributes, config, base_url, name, 'linkedin',
'id')

def start_auth(self, context, internal_request, get_state=stateID):
def start_auth(self, context, internal_request, get_state=stateID, **kwargs):
"""
:param get_state: Generates a state to be used in authentication call
Expand Down Expand Up @@ -76,7 +76,7 @@ def auth_info(self, requrest):
UNSPECIFIED, None,
self.config['server_info']['authorization_endpoint'])

def _authn_response(self, context):
def _authn_response(self, context, **kwargs):
state_data = context.state[self.name]
aresp = self.consumer.parse_response(
AuthorizationResponse, info=json.dumps(context.request))
Expand Down
4 changes: 2 additions & 2 deletions src/satosa/backends/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name, extern
response_type=self.config["response_type"])
self.consumer.client_secret = self.config["client_secret"]

def start_auth(self, context, internal_request, get_state=stateID):
def start_auth(self, context, internal_request, get_state=stateID, **kwargs):
"""
See super class method satosa.backends.base#start_auth
:param get_state: Generates a state to be used in the authentication call.
Expand Down Expand Up @@ -118,7 +118,7 @@ def _verify_state(self, resp, state_data, state):
"Missing or invalid state [%s] in response!" %
received_state)

def _authn_response(self, context):
def _authn_response(self, context, **kwargs):
"""
Handles the authentication response from the AS.
Expand Down
4 changes: 2 additions & 2 deletions src/satosa/backends/openid_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __init__(self, auth_callback_func, internal_attributes, config, base_url, na
if "response_type" not in config["client"]["auth_req_params"]:
config["auth_req_params"]["response_type"] = "code"

def start_auth(self, context, request_info):
def start_auth(self, context, request_info, **kwargs):
"""
See super class method satosa.backends.base#start_auth
:type context: satosa.context.Context
Expand Down Expand Up @@ -167,7 +167,7 @@ def _get_userinfo(self, state, context):
self._check_error_response(userinfo_resp, context)
return userinfo_resp.to_dict()

def response_endpoint(self, context, *args):
def response_endpoint(self, context, *args, **kwargs):
"""
Handles the authentication response from the OP.
:type context: satosa.context.Context
Expand Down
2 changes: 1 addition & 1 deletion src/satosa/backends/orcid.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def auth_info(self, requrest):
UNSPECIFIED, None,
self.config['server_info']['authorization_endpoint'])

def _authn_response(self, context):
def _authn_response(self, context, **kwargs):
state_data = context.state[self.name]
aresp = self.consumer.parse_response(
AuthorizationResponse, info=json.dumps(context.request))
Expand Down
25 changes: 13 additions & 12 deletions src/satosa/backends/saml2.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
with open(p) as key_file:
self.encryption_keys.append(key_file.read())

def get_idp_entity_id(self, context):
def get_idp_entity_id(self, context, **kwargs):
"""
:type context: satosa.context.Context
:rtype: str | None
Expand Down Expand Up @@ -164,7 +164,7 @@ def get_idp_entity_id(self, context):
)
return entity_id

def start_auth(self, context, internal_req):
def start_auth(self, context, internal_req, **kwargs):
"""
See super class method satosa.backends.base.BackendModule#start_auth
Expand All @@ -184,7 +184,7 @@ def start_auth(self, context, internal_req):

return self.authn_request(context, entity_id)

def disco_query(self, context):
def disco_query(self, context, **kwargs):
"""
Makes a request to the discovery server
Expand Down Expand Up @@ -236,7 +236,7 @@ def construct_requested_authn_context(self, entity_id):

return authn_context

def authn_request(self, context, entity_id):
def authn_request(self, context, entity_id, **kwargs):
"""
Do an authorization request on idp with given entity id.
This is the start of the authorization.
Expand Down Expand Up @@ -294,7 +294,7 @@ def authn_request(self, context, entity_id):
context.state[self.name] = {"relay_state": relay_state}
return make_saml_response(binding, ht_args)

def authn_response(self, context, binding):
def authn_response(self, context, binding, **kwargs):
"""
Endpoint for the idp response
:type context: satosa.context,Context
Expand Down Expand Up @@ -326,11 +326,12 @@ def authn_response(self, context, binding):
raise SATOSAAuthenticationError(context.state, errmsg)
del self.outstanding_queries[req_id]

# check if the relay_state matches the cookie state
if context.state[self.name]["relay_state"] != context.request["RelayState"]:
satosa_logging(logger, logging.DEBUG,
"State did not match relay state for state", context.state)
raise SATOSAAuthenticationError(context.state, "State did not match relay state")
# if the response relay_state exists it must match that from the request
if self.name in context.state and "relay_state" in context.state[self.name]:
if context.state[self.name]["relay_state"] != context.request["RelayState"]:
logger.debug("State did not match relay state for state", extra={'state': context.state})
raise SATOSAAuthenticationError(context.state, "State did not match relay state")
del context.state[self.name]

context.decorate(Context.KEY_BACKEND_METADATA_STORE, self.sp.metadata)
if self.config.get(SAMLBackend.KEY_MEMORIZE_IDP):
Expand All @@ -340,7 +341,7 @@ def authn_response(self, context, binding):
context.state.pop(Context.KEY_FORCE_AUTHN, None)
return self.auth_callback_func(context, self._translate_response(authn_response, context.state))

def disco_response(self, context):
def disco_response(self, context, **kwargs):
"""
Endpoint for the discovery server response
Expand Down Expand Up @@ -406,7 +407,7 @@ def _translate_response(self, response, state):
json.dumps(response.ava, indent=4), state)
return internal_resp

def _metadata_endpoint(self, context):
def _metadata_endpoint(self, context, **kwargs):
"""
Endpoint for retrieving the backend metadata
:type context: satosa.context.Context
Expand Down
2 changes: 1 addition & 1 deletion src/satosa/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def _run_bound_endpoint(self, context, spec):
:return: response
"""
try:
return spec(context)
return spec(context, wsgi_app=self)
except SATOSAAuthenticationError as error:
error.error_id = uuid.uuid4().urn
state = json.dumps(error.state.state_dict, indent=4)
Expand Down
2 changes: 1 addition & 1 deletion src/satosa/frontends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, auth_req_callback_func, internal_attributes, base_url, name):
self.base_url = base_url
self.name = name

def handle_authn_response(self, context, internal_resp):
def handle_authn_response(self, context, internal_resp, **kwargs):
"""
If an authorization has been successful in a backend, this function is called and is
supposed to send an authorization response to the client.
Expand Down
16 changes: 8 additions & 8 deletions src/satosa/frontends/openid_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _init_authorization_state(self):
return AuthorizationState(HashBasedSubjectIdentifierFactory(sub_hash_salt), authz_code_db, access_token_db,
refresh_token_db, sub_db, **token_lifetimes)

def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None):
def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None, **kwargs):
"""
See super class method satosa.frontends.base.FrontendModule#handle_authn_response
:type context: satosa.context.Context
Expand Down Expand Up @@ -232,7 +232,7 @@ def _get_authn_request_from_state(self, state):
"""
return AuthorizationRequest().deserialize(state[self.name]["oidc_request"])

def client_registration(self, context):
def client_registration(self, context, **kwargs):
"""
Handle the OIDC dynamic client registration.
:type context: satosa.context.Context
Expand All @@ -247,7 +247,7 @@ def client_registration(self, context):
except InvalidClientRegistrationRequest as e:
return BadRequest(e.to_json(), content="application/json")

def provider_config(self, context):
def provider_config(self, context, **kwargs):
"""
Construct the provider configuration information (served at /.well-known/openid-configuration).
:type context: satosa.context.Context
Expand All @@ -270,7 +270,7 @@ def _get_approved_attributes(self, provider_supported_claims, authn_req):
requested_claims.extend(authn_req["claims"][k].keys())
return set(provider_supported_claims).intersection(set(requested_claims))

def _handle_authn_request(self, context):
def _handle_authn_request(self, context, **kwargs):
"""
Parse and verify the authentication request into an internal request.
:type context: satosa.context.Context
Expand Down Expand Up @@ -315,7 +315,7 @@ def _handle_authn_request(self, context):
authn_req))
return internal_req

def handle_authn_request(self, context):
def handle_authn_request(self, context, **kwargs):
"""
Handle an authentication request and pass it on to the backend.
:type context: satosa.context.Context
Expand All @@ -329,7 +329,7 @@ def handle_authn_request(self, context):
return internal_req
return self.auth_req_callback_func(context, internal_req)

def jwks(self, context):
def jwks(self, context, **kwargs):
"""
Construct the JWKS document (served at /jwks).
:type context: satosa.context.Context
Expand All @@ -340,7 +340,7 @@ def jwks(self, context):
"""
return Response(json.dumps(self.provider.jwks), content="application/json")

def token_endpoint(self, context):
def token_endpoint(self, context, **kwargs):
"""
Handle token requests (served at /token).
:type context: satosa.context.Context
Expand All @@ -364,7 +364,7 @@ def token_endpoint(self, context):
error_resp = TokenErrorResponse(error=e.oauth_error, error_description=str(e))
return BadRequest(error_resp.to_json(), content="application/json")

def userinfo_endpoint(self, context):
def userinfo_endpoint(self, context, **kwargs):
headers = {"Authorization": context.request_authorization}

try:
Expand Down
4 changes: 2 additions & 2 deletions src/satosa/frontends/ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, auth_req_callback_func, internal_attributes, config, base_url

self.config = config

def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None):
def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None, **kwargs):
"""
See super class method satosa.frontends.base.FrontendModule#handle_authn_response
:type context: satosa.context.Context
Expand Down Expand Up @@ -47,7 +47,7 @@ def register_endpoints(self, backend_names):

return url_map

def ping_endpoint(self, context):
def ping_endpoint(self, context, **kwargs):
"""
"""
logprefix = PingFrontend.logprefix
Expand Down
Loading