Skip to content

Commit b54006f

Browse files
committed
Satosa now hashes the user id depending on hash type
1 parent 5c36a83 commit b54006f

File tree

8 files changed

+69
-18
lines changed

8 files changed

+69
-18
lines changed

src/satosa/backends/saml2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def disco_response(self, context, *args):
166166
return Unauthorized("You must chose an IdP")
167167
else:
168168
state = json.loads(state)
169-
request_info = InternalRequest(getattr(UserIdHashType, state["user_id_hash_type"]))
169+
request_info = InternalRequest(getattr(UserIdHashType, state["user_id_hash_type"]), None)
170170
return self.authn_request(context, entity_id, request_info, state["state"])
171171

172172
def _translate_response(self, response):

src/satosa/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
The SATOSA main module
33
"""
4+
from satosa.internal_data import UserIdHasher
45
from satosa.plugin_loader import load_backends, load_frontends, load_micro_services
56
from satosa.routing import ModuleRouter
67

@@ -49,6 +50,7 @@ def _auth_req_callback_func(self, context, internal_request, state):
4950
5051
:return: response
5152
"""
53+
state = UserIdHasher.save_state(internal_request, state)
5254
backend, state = self.module_router.backend_routing(context, state)
5355
context.request = None
5456
if self.request_micro_services:
@@ -68,9 +70,9 @@ def _auth_resp_callback_func(self, context, internal_response, state):
6870
:param state: The current state
6971
:return: response
7072
"""
71-
72-
frontend, state = self.module_router.frontend_routing(state)
73+
frontend, state = self.module_router.frontend_routing(context, state)
7374
context.request = None
75+
internal_response, state = UserIdHasher.set_id(internal_response, state)
7476
if self.response_micro_services:
7577
internal_response = self.response_micro_services.process_service_queue(context, internal_response)
7678
return frontend.handle_authn_response(context, internal_response, state)

src/satosa/frontends/saml2.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def get_name_id_format(hash_type):
5454
return None
5555

5656
def save_state(self, context, _dict, _request):
57-
return {"origin_authn_req": _dict["authn_req"].to_string().decode("utf-8"),
58-
"relay_state": _request["RelayState"]}
57+
return {"origin_authn_req": _dict["authn_req"].to_string().decode("utf-8"),
58+
"relay_state": _request["RelayState"]}
5959

6060
def load_state(self, state):
6161
return json.loads(urlsafe_b64decode(state.encode("UTF-8")).decode("UTF-8"))
@@ -150,7 +150,8 @@ def _handle_authn_request(self, context, binding_in, idp):
150150
state = urlsafe_b64encode(json.dumps(request_state).encode("UTF-8")).decode(
151151
"UTF-8")
152152

153-
internal_req = InternalRequest(self.name_format_to_hash_type(_dict['req_args']['name_id_policy'].format))
153+
internal_req = InternalRequest(self.name_format_to_hash_type(_dict['req_args']['name_id_policy'].format),
154+
_dict["resp_args"]["sp_entity_id"])
154155
return self.auth_req_callback_func(context, internal_req, state)
155156

156157
def handle_authn_request(self, context, binding_in):

src/satosa/internal_data.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
The module contains internal data representation in SATOSA and general converteras that can be used
33
for converting from SAML/OAuth/OpenID connect to the internal representation.
44
"""
5+
from base64 import urlsafe_b64encode, urlsafe_b64decode
6+
import datetime
57
from enum import Enum
8+
import hashlib
9+
import json
610

711
__author__ = 'haho0032'
812

@@ -360,9 +364,40 @@
360364

361365
class UserIdHashType(Enum):
362366
transient = 1
363-
persistent = 3
364-
pairwise = 1
365-
public = 2
367+
persistent = 2
368+
pairwise = 2
369+
public = 3
370+
371+
372+
class UserIdHasher():
373+
@staticmethod
374+
def save_state(internal_request, state):
375+
new_state = {"state": state,
376+
"requestor": internal_request.requestor}
377+
return urlsafe_b64encode(json.dumps(new_state).encode("UTF-8")).decode("UTF-8")
378+
379+
@staticmethod
380+
def set_id(internal_response, state):
381+
state = json.loads(urlsafe_b64decode(state.encode("UTF-8")).decode("UTF-8"))
382+
requestor = state["requestor"]
383+
user_id = internal_response.user_id
384+
user_id_hash_type = internal_response.user_id_hash_type
385+
386+
if user_id_hash_type == UserIdHashType.transient:
387+
timestamp = datetime.datetime.now().time()
388+
user_id = "{req}{time}{id}".format(req=requestor, time=timestamp, id=user_id)
389+
elif user_id_hash_type == UserIdHashType.persistent:
390+
user_id = "{req}{id}".format(req=requestor, id=user_id)
391+
elif user_id_hash_type == UserIdHashType.pairwise:
392+
user_id = "{req}{id}".format(req=requestor, id=user_id)
393+
elif user_id_hash_type == UserIdHashType.public:
394+
user_id = "{id}".format(id=user_id)
395+
else:
396+
raise ValueError("Unknown id hash type: '{}'".format(user_id_hash_type))
397+
398+
internal_response.user_id = hashlib.md5(user_id.encode("utf-8")).hexdigest()
399+
400+
return (internal_response, state["state"])
366401

367402

368403
class AuthenticationInformation(object):
@@ -378,8 +413,17 @@ def __init__(self, user_id_hash_type):
378413

379414

380415
class InternalRequest(InternalData):
381-
def __init__(self, user_id_hash_type):
416+
def __init__(self, user_id_hash_type, requestor):
417+
"""
418+
419+
:param user_id_hash_type:
420+
:param requestor: identifier of the requestor
421+
422+
:type user_id_hash_type: UserIdHashType
423+
:type requestor: str
424+
"""
382425
super(InternalRequest, self).__init__(user_id_hash_type)
426+
self.requestor = requestor
383427

384428

385429
class InternalResponse(InternalData):

src/satosa/routing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,22 @@ def backend_routing(self, context, state):
7171
satosa_state = urlsafe_b64encode(json.dumps(satosa_state).encode("UTF-8")).decode("UTF-8")
7272
return backend, satosa_state
7373

74-
def frontend_routing(self, state):
74+
def frontend_routing(self, context, state):
7575
"""
7676
Returns the targeted frontend and original state
7777
78+
:type context: satosa.context.Context
7879
:type state: str
7980
:rtype (satosa.frontends.base.FrontendModule, str)
8081
82+
:param context: The response context
8183
:param state: The state created in the incoming function
8284
:return: (frontend, state)
8385
"""
8486

8587
unpacked_state = json.loads(urlsafe_b64decode(state.encode("UTF-8")).decode("UTF-8"))
86-
frontend = self.frontends[unpacked_state["frontend"]]["instance"]
88+
context._target_frontend = unpacked_state["frontend"]
89+
frontend = self.frontends[context._target_frontend]["instance"]
8790
request_state = unpacked_state["state_key"]
8891
return frontend, request_state
8992

tests/satosa/backends/test_saml2.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_start_auth_no_request_info():
138138
None,
139139
TestConfiguration.get_instance().spconfig)
140140

141-
internal_data = InternalRequest(None)
141+
internal_data = InternalRequest(None, None)
142142

143143
resp = samlbackend.start_auth(Context(), internal_data, "my_state")
144144
assert resp.status == "303 See Other", "Must be a redirect to the discovery server."
@@ -147,7 +147,7 @@ def test_start_auth_no_request_info():
147147

148148
# create_name_id_policy_transient()
149149
user_id_hash_type = UserIdHashType.transient
150-
internal_data = InternalRequest(user_id_hash_type)
150+
internal_data = InternalRequest(user_id_hash_type, None)
151151
resp = samlbackend.start_auth(Context(), internal_data, "my_state")
152152
assert resp.status == "303 See Other", "Must be a redirect to the discovery server."
153153

@@ -162,7 +162,7 @@ def test_start_auth_name_id_policy():
162162

163163
# name_id_policy = create_name_id_policy_transient()
164164
# request_info = {"req_args": {"name_id_policy": name_id_policy}}
165-
internal_req = InternalRequest(UserIdHashType.transient)
165+
internal_req = InternalRequest(UserIdHashType.transient, None)
166166
resp = samlbackend.start_auth(Context(), internal_req, "my_state")
167167

168168
assert resp.status == "303 See Other", "Must be a redirect to the discovery server."
@@ -222,7 +222,7 @@ def auth_req_callback_func(context, internal_resp, state):
222222
# name_id_policy = create_name_id_policy_persistent()
223223
# request_info = {"req_args": {"name_id_policy": name_id_policy}}
224224

225-
internal_req = InternalRequest(UserIdHashType.persistent)
225+
internal_req = InternalRequest(UserIdHashType.persistent, "example.se/sp.xml")
226226

227227
resp = samlbackend.start_auth(Context(), internal_req, "my_state")
228228
assert resp.status == "303 See Other", "Must be a redirect to the discovery server."

tests/satosa/frontends/test_saml2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def auth_req_callback_func(context, internal_req, state):
126126
:param state: The current state for the module.
127127
:return:
128128
"""
129-
129+
assert internal_req.requestor == SPCONFIG["entityid"]
130130
auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml")
131131
internal_response = InternalResponse(internal_req.user_id_hash_type, auth_info=auth_info)
132132
internal_response.add_pysaml_attributes(USERS["testuser1"])

tests/satosa/test_routing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ def test_routing(path, provider, receiver, _):
102102
backend, backend_state = router.backend_routing(context, original_state)
103103
assert backend == backends[provider]
104104

105-
frontend, frontend_state = router.frontend_routing(backend_state)
105+
frontend, frontend_state = router.frontend_routing(context, backend_state)
106106
assert frontend == frontends[receiver]
107107
assert frontend_state == original_state
108+
assert context._target_frontend == receiver
108109

109110
foreach_frontend_endpoint(test_routing)
110111

0 commit comments

Comments
 (0)