Skip to content

Commit 1206ea5

Browse files
committed
feat: make cookie parameters configurable
Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 4041df2 commit 1206ea5

File tree

5 files changed

+51
-29
lines changed

5 files changed

+51
-29
lines changed

doc/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ bind_password: !ENVFILE LDAP_BIND_PASSWORD_FILE
8080
| -------------- | --------- | ------------- | ----------- |
8181
| `BASE` | string | `https://proxy.example.com` | base url of the proxy |
8282
| `COOKIE_STATE_NAME` | string | `satosa_state` | name of the cookie SATOSA uses for preserving state between requests |
83+
| `COOKIE_SECURE` | bool | `True` | whether to include the cookie only when the request is transmitted over a secure channel |
84+
| `COOKIE_HTTPONLY` | bool | `True` | whether the cookie should only be accessed only by the server |
85+
| `COOKIE_SAMESITE` | string | `"None"` | whether the cookie should only be sent with requests initiated from the same registrable domain |
86+
| `COOKIE_MAX_AGE` | string | `"1200"` | indicates the maximum lifetime of the cookie represented as the number of seconds until the cookie expires |
8387
| `CONTEXT_STATE_DELETE` | bool | `True` | controls whether SATOSA will delete the state cookie after receiving the authentication response from the upstream IdP|
8488
| `STATE_ENCRYPTION_KEY` | string | `52fddd3528a44157` | key used for encrypting the state cookie, will be overridden by the environment variable `SATOSA_STATE_ENCRYPTION_KEY` if it is set |
8589
| `INTERNAL_ATTRIBUTES` | string | `example/internal_attributes.yaml` | path to attribute mapping

src/satosa/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def _load_state(self, context):
204204
state = State()
205205
finally:
206206
context.state = state
207-
msg = "Loaded state {state} from cookie {cookie}".format(state=state, cookie=context.cookie)
207+
msg = f"Loaded state {state} from cookie {context.cookie}"
208208
logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg)
209209
logger.debug(logline)
210210

@@ -225,6 +225,10 @@ def _save_state(self, resp, context):
225225
name=cookie_name,
226226
path="/",
227227
encryption_key=self.config["STATE_ENCRYPTION_KEY"],
228+
secure=self.config.get("COOKIE_SECURE"),
229+
httponly=self.config.get("COOKIE_HTTPONLY"),
230+
samesite=self.config.get("COOKIE_SAMESITE"),
231+
max_age=self.config.get("COOKIE_MAX_AGE"),
228232
)
229233
resp.headers = [
230234
(name, value)

src/satosa/satosa_config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __init__(self, config):
4040

4141
# Load sensitive config from environment variables
4242
for key in SATOSAConfig.sensitive_dict_keys:
43-
val = os.environ.get("SATOSA_{key}".format(key=key))
43+
val = os.environ.get(f"SATOSA_{key}")
4444
if val:
4545
self._config[key] = val
4646

@@ -56,7 +56,7 @@ def __init__(self, config):
5656
plugin_configs.append(plugin_config)
5757
break
5858
else:
59-
raise SATOSAConfigurationError('Failed to load plugin config \'{}\''.format(config))
59+
raise SATOSAConfigurationError(f"Failed to load plugin config '{config}'")
6060
self._config[key] = plugin_configs
6161

6262
for parser in parsers:
@@ -86,8 +86,8 @@ def _verify_dict(self, conf):
8686
raise SATOSAConfigurationError("Missing key '%s' in config" % key)
8787

8888
for key in SATOSAConfig.sensitive_dict_keys:
89-
if key not in conf and "SATOSA_{key}".format(key=key) not in os.environ:
90-
raise SATOSAConfigurationError("Missing key '%s' from config and ENVIRONMENT" % key)
89+
if key not in conf and f"SATOSA_{key}" not in os.environ:
90+
raise SATOSAConfigurationError(f"Missing key '{key}' from config and ENVIRONMENT")
9191

9292
def __getitem__(self, item):
9393
"""

src/satosa/state.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,31 +128,46 @@ def state_dict(self):
128128
return copy.deepcopy(self.data)
129129

130130

131-
def state_to_cookie(state, name, path, encryption_key):
131+
def state_to_cookie(
132+
state: State,
133+
*,
134+
name: str,
135+
path: str,
136+
encryption_key: str,
137+
secure: bool = None,
138+
httponly: bool = None,
139+
samesite: str = None,
140+
max_age: str = None,
141+
) -> SimpleCookie:
132142
"""
133143
Saves a state to a cookie
134144
135-
:type state: satosa.state.State
136-
:type name: str
137-
:type path: str
138-
:type encryption_key: str
139-
:rtype: satosa.cookies.SimpleCookie
140-
141-
:param state: The state to save
142-
:param name: Name identifier of the cookie
143-
:param path: Endpoint path the cookie will be associated to
144-
:param encryption_key: Key to encrypt the state information
145-
:return: A cookie
145+
:param state: the data to save
146+
:param name: identifier of the cookie
147+
:param path: path the cookie will be associated to
148+
:param encryption_key: the key to use to encrypt the state information
149+
:param secure: whether to include the cookie only when the request is transmitted
150+
over a secure channel
151+
:param httponly: whether the cookie should only be accessed only by the server
152+
:param samesite: whether the cookie should only be sent with requests
153+
initiated from the same registrable domain
154+
:param max_age: indicates the maximum lifetime of the cookie,
155+
represented as the number of seconds until the cookie expires
156+
:return: A cookie object
146157
"""
147-
148-
cookie_data = "" if state.delete else state.urlstate(encryption_key)
149-
150158
cookie = SimpleCookie()
151-
cookie[name] = cookie_data
152-
cookie[name]["samesite"] = "None"
153-
cookie[name]["secure"] = True
159+
cookie[name] = "" if state.delete else state.urlstate(encryption_key)
154160
cookie[name]["path"] = path
155-
cookie[name]["max-age"] = 0 if state.delete else ""
161+
cookie[name]["secure"] = secure if secure is not None else True
162+
cookie[name]["httponly"] = httponly if httponly is not None else ""
163+
cookie[name]["samesite"] = samesite if samesite is not None else "None"
164+
cookie[name]["max-age"] = (
165+
0
166+
if state.delete
167+
else max_age
168+
if max_age is not None
169+
else ""
170+
)
156171

157172
msg = "Saved state in cookie {name} with properties {props}".format(
158173
name=name, props=list(cookie[name].items())
@@ -163,7 +178,7 @@ def state_to_cookie(state, name, path, encryption_key):
163178
return cookie
164179

165180

166-
def cookie_to_state(cookie_str, name, encryption_key):
181+
def cookie_to_state(cookie_str: str, name: str, encryption_key: str) -> State:
167182
"""
168183
Loads a state from a cookie
169184
@@ -181,8 +196,7 @@ def cookie_to_state(cookie_str, name, encryption_key):
181196
cookie = SimpleCookie(cookie_str)
182197
state = State(cookie[name].value, encryption_key)
183198
except KeyError as e:
184-
msg_tmpl = 'No cookie named {name} in {data}'
185-
msg = msg_tmpl.format(name=name, data=cookie_str)
199+
msg = f'No cookie named {name} in {cookie_str}'
186200
raise SATOSAStateError(msg) from e
187201
except ValueError as e:
188202
msg_tmpl = 'Failed to process {name} from {data}'

tests/satosa/test_state.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_encode_decode_of_state(self):
100100
path = "/"
101101
encrypt_key = "2781y4hef90"
102102

103-
cookie = state_to_cookie(state, cookie_name, path, encrypt_key)
103+
cookie = state_to_cookie(state, name=cookie_name, path=path, encryption_key=encrypt_key)
104104
cookie_str = cookie[cookie_name].OutputString()
105105
loaded_state = cookie_to_state(cookie_str, cookie_name, encrypt_key)
106106

@@ -117,7 +117,7 @@ def test_state_to_cookie_produces_cookie_without_max_age_for_state_that_should_b
117117
path = "/"
118118
encrypt_key = "2781y4hef90"
119119

120-
cookie = state_to_cookie(state, cookie_name, path, encrypt_key)
120+
cookie = state_to_cookie(state, name=cookie_name, path=path, encryption_key=encrypt_key)
121121
cookie_str = cookie[cookie_name].OutputString()
122122

123123
parsed_cookie = SimpleCookie(cookie_str)

0 commit comments

Comments
 (0)