forked from IdentityPython/SATOSA
-
Notifications
You must be signed in to change notification settings - Fork 1
/
redirect_url.py
116 lines (95 loc) · 4.88 KB
/
redirect_url.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""
ADFS/SAML-Support for role selection and profile completion after a SAML-Response
was issued using a redirect-to-idp flow.
* Store AuthnRequest for later replay
* Handle redirect-to-idp and replay AuthnRequest after redirect-to-idp flow
Persist state: Storing the the full context of the AuthnRequest in SATOSA_STATE is not feasible due to cookie size limitations.
Instead, it is stored in a local redis store, and the key is stored in SATOSA_STATE.
The Redis interface is using a basic implementation creating a connection pool and TCP sockets for each call, which is OK for the modest deployment.
(Instantiating a global connection pool across gunicorn worker threads would impose some additional complexity.)
The AuthnRequest is stored unencrypted with the assumption that a stolen request cannot do harm,
because the final Response will only be delivered to the metadata-specified ACS endpoint.
"""
import logging
import pickle
import sys
from typing import Tuple
import redis
import satosa
from .base import RequestMicroService, ResponseMicroService
from satosa.state import _AESCipher
MIN_PYTHON = (3, 6)
if sys.version_info < MIN_PYTHON:
sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON)
STATE_KEY = "REDIRURLCONTEXT"
class LocalStore():
""" Store context objects in Redis.
Create a new key when a new value is set.
Delete key/value after reading it
"""
def __init__(self, encryption_key: str):
self.redis = redis.Redis(host='localhost', port=6379)
self.aes_cipher = _AESCipher(encryption_key)
def set(self, context: object) -> int:
context_serlzd = pickle.dumps(context, pickle.HIGHEST_PROTOCOL)
context_enc = self.aes_cipher.encrypt(context_serlzd)
key = self.redis.incr('REDIRURL_sequence', 1)
self.redis.set(key, context_serlzd, 1800) # generous 30 min timeout to complete SSO transaction
return key
def get(self, key: int) -> object:
context_serlzd = self.redis.get(key)
self.redis.expire(key, 600) # delay deletion in case request is repeated due to network issues
return pickle.loads(context_serlzd)
from autologging import traced
@traced()
class RedirectUrlRequest(RequestMicroService):
""" Store AuthnRequest in SATOSA STATE in case it is required later for the RedirectUrl flow """
def __init__(self, config: dict, *args, **kwargs):
super().__init__(*args, **kwargs)
self.local_store = LocalStore(config['db_encryption_key'])
logging.info('RedirectUrlRequest microservice active')
def process(
self, context: satosa.context.Context, internal_request: satosa.internal.InternalData) \
-> Tuple[satosa.context.Context, satosa.internal.InternalData]:
key = self.local_store.set(context)
context.state[STATE_KEY] = str(key)
logging.debug(f"RedirectUrlRequest: store context (stub)")
return super().process(context, internal_request)
@traced()
class RedirectUrlResponse(ResponseMicroService):
"""
Handle following events:
* Processing a SAML Response:
if the redirectUrl attribute is set in the response/attribute statement:
Redirect to responder
* Processing a RedirectUrlResponse:
Retrieve previously saved AuthnRequest
Replay AuthnRequest
"""
def __init__(self, config: dict, *args, **kwargs):
super().__init__(*args, **kwargs)
self.endpoint = 'redirecturl_response'
self.redir_attr = config['redirect_attr_name']
self.redir_entityid = config['redir_entityid']
self.local_store = LocalStore(config['db_encryption_key'])
logging.info('RedirectUrlResponse microservice active')
def _handle_redirecturl_response(
self,
context: satosa.context.Context,
wsgi_app: callable(satosa.context.Context)) -> satosa.response.Response:
logging.debug(f"RedirectUrl microservice: RedirectUrl processing complete")
key = int(context.state[STATE_KEY])
authnrequ_context = self.local_store.get(key)
resp = wsgi_app.run(authnrequ_context)
return resp
def process(self, context: satosa.context.Context,
internal_response: satosa.internal.InternalData) -> satosa.response.Response:
if self.redir_attr in internal_response.attributes:
logging.debug(f"RedirectUrl microservice: Attribute {self.redir_attr} found, starting redirect")
redirecturl = internal_response.attributes[self.redir_attr][0] + '?wtrealm=' + 'https%3A%2F%2Fproxy2.test.wpv.portalverbund.at%2Fsp%2Fproxy_backend.xml'
return satosa.response.Redirect(redirecturl)
else:
logging.debug(f"RedirectUrl microservice: Attribute {self.redir_attr} not found")
return super().process(context, context)
def register_endpoints(self):
return [("^{}$".format(self.endpoint), self._handle_redirecturl_response), ]