/src/mod_auth_openidc/src/session.c
Line | Count | Source |
1 | | /* |
2 | | * Licensed to the Apache Software Foundation (ASF) under one |
3 | | * or more contributor license agreements. See the NOTICE file |
4 | | * distributed with this work for additional information |
5 | | * regarding copyright ownership. The ASF licenses this file |
6 | | * to you under the Apache License, Version 2.0 (the |
7 | | * "License"); you may not use this file except in compliance |
8 | | * with the License. You may obtain a copy of the License at |
9 | | * |
10 | | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | | * |
12 | | * Unless required by applicable law or agreed to in writing, |
13 | | * software distributed under the License is distributed on an |
14 | | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | | * KIND, either express or implied. See the License for the |
16 | | * specific language governing permissions and limitations |
17 | | * under the License. |
18 | | */ |
19 | | |
20 | | /*************************************************************************** |
21 | | * Copyright (C) 2017-2026 ZmartZone Holding BV |
22 | | * Copyright (C) 2013-2017 Ping Identity Corporation |
23 | | * All rights reserved. |
24 | | * |
25 | | * DISCLAIMER OF WARRANTIES: |
26 | | * |
27 | | * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT |
28 | | * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, |
29 | | * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, |
30 | | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY |
31 | | * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE |
32 | | * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET |
33 | | * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE |
34 | | * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
35 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
36 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF |
37 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
38 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
39 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | | * |
41 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
42 | | */ |
43 | | |
44 | | #include "session.h" |
45 | | #include "cfg/dir.h" |
46 | | #include "jose.h" |
47 | | #include "metrics.h" |
48 | | #include "util/util.h" |
49 | | #include "util/util_cfg.h" |
50 | | |
51 | | /* the name of the remote-user attribute in the session */ |
52 | 0 | #define OIDC_SESSION_REMOTE_USER_KEY "r" |
53 | | /* the name of the session expiry attribute in the session */ |
54 | 0 | #define OIDC_SESSION_EXPIRY_KEY "e" |
55 | | /* the name of the session identifier in the session */ |
56 | 0 | #define OIDC_SESSION_SESSION_ID "i" |
57 | | /* the name of the sub attribute in the session */ |
58 | 0 | #define OIDC_SESSION_SUB_KEY "sub" |
59 | | /* the name of the sid attribute in the session */ |
60 | 0 | #define OIDC_SESSION_SID_KEY "sid" |
61 | | /* key for storing the userinfo claims in the session context */ |
62 | 0 | #define OIDC_SESSION_KEY_USERINFO_CLAIMS "uic" |
63 | | /* key for storing the id_token claims in the session context */ |
64 | 0 | #define OIDC_SESSION_KEY_IDTOKEN_CLAIMS "idc" |
65 | | /* the name of the payload format version attribute in the session */ |
66 | 0 | #define OIDC_SESSION_FORMAT_VERSION_KEY "v" |
67 | | |
68 | | /* |
69 | | * Bump only when older readers cannot safely interpret the payload. Adding keys is compatible |
70 | | * because readers ignore unknown keys. A missing version means the original, version-1 layout. |
71 | | */ |
72 | 0 | #define OIDC_SESSION_FORMAT_VERSION 1 |
73 | | |
74 | | /* |
75 | | * encode/serialize the session object/data into a string, possibly a serialized encrypted JWT when encryption is |
76 | | * requested |
77 | | */ |
78 | | static apr_byte_t oidc_session_encode(request_rec *r, const oidc_cfg_t *c, const oidc_session_t *z, char **s_value, |
79 | 0 | apr_byte_t encrypt) { |
80 | |
|
81 | 0 | if (encrypt == FALSE) { |
82 | 0 | *s_value = oidc_json_encode(r->pool, z->state, OIDC_JSON_COMPACT); |
83 | 0 | return (*s_value != NULL); |
84 | 0 | } else if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) { |
85 | 0 | oidc_error(r, "cannot encrypt session state because " OIDCCryptoPassphrase " is not set"); |
86 | 0 | return FALSE; |
87 | 0 | } |
88 | | |
89 | 0 | if (oidc_util_jwt_create(r, oidc_cfg_crypto_passphrase_get(c), |
90 | 0 | oidc_json_encode(r->pool, z->state, OIDC_JSON_COMPACT), s_value) == FALSE) |
91 | 0 | return FALSE; |
92 | | |
93 | 0 | return TRUE; |
94 | 0 | } |
95 | | |
96 | | /* Reject payloads from newer layouts so the caller re-authenticates instead of misreading them. */ |
97 | 0 | static apr_byte_t oidc_session_version_supported(request_rec *r, oidc_session_t *z) { |
98 | 0 | int version = 0; |
99 | | /* absent: written before the field existed, i.e. the original layout */ |
100 | 0 | oidc_json_object_get_int(z->state, OIDC_SESSION_FORMAT_VERSION_KEY, &version, 1); |
101 | 0 | if (version > OIDC_SESSION_FORMAT_VERSION) { |
102 | 0 | oidc_warn(r, |
103 | 0 | "discarding a session in payload format version %d: this module writes and understands " |
104 | 0 | "up to version %d, so it was written by a newer mod_auth_openidc sharing this cache", |
105 | 0 | version, OIDC_SESSION_FORMAT_VERSION); |
106 | | /* the rejected object is refcounted, not pool-owned: drop it here, because the |
107 | | * callers treat FALSE as "no session" and a fallback load |
108 | | * (OIDCSessionCacheFallbackToCookie) overwrites the pointer and leaks it */ |
109 | 0 | oidc_json_decref(z->state); |
110 | 0 | z->state = NULL; |
111 | 0 | return FALSE; |
112 | 0 | } |
113 | 0 | return TRUE; |
114 | 0 | } |
115 | | |
116 | | /* |
117 | | * parse a session object from the provided string, which may be an encrypted JWT is encryption is on |
118 | | */ |
119 | | static apr_byte_t oidc_session_decode(request_rec *r, const oidc_cfg_t *c, oidc_session_t *z, const char *s_json, |
120 | 0 | apr_byte_t encrypt) { |
121 | 0 | char *s_payload = NULL; |
122 | |
|
123 | 0 | if (encrypt == FALSE) { |
124 | 0 | if (oidc_json_decode_object(r, s_json, &z->state) == FALSE) |
125 | 0 | return FALSE; |
126 | 0 | return oidc_session_version_supported(r, z); |
127 | 0 | } else if (oidc_cfg_crypto_passphrase_secret1_get(c) == NULL) { |
128 | 0 | oidc_error(r, "cannot decrypt session state because " OIDCCryptoPassphrase " is not set"); |
129 | 0 | return FALSE; |
130 | 0 | } |
131 | | |
132 | 0 | if (oidc_util_jwt_verify(r, oidc_cfg_crypto_passphrase_get(c), s_json, &s_payload) == FALSE) { |
133 | 0 | oidc_error(r, "could not verify secure JWT: cache value possibly corrupted"); |
134 | 0 | return FALSE; |
135 | 0 | } |
136 | | |
137 | 0 | if (oidc_json_decode_object(r, s_payload, &z->state) == FALSE) |
138 | 0 | return FALSE; |
139 | | |
140 | 0 | return oidc_session_version_supported(r, z); |
141 | 0 | } |
142 | | |
143 | 0 | #define OIDC_SESSION_ID_LEN 20 |
144 | | |
145 | | /* |
146 | | * generate a unique identifier for a session |
147 | | */ |
148 | 0 | void oidc_session_id_new(request_rec *r, oidc_session_t *z) { |
149 | 0 | z->uuid = oidc_util_rand_hex_str(r, r->pool, OIDC_SESSION_ID_LEN); |
150 | 0 | } |
151 | | |
152 | | /* |
153 | | * clear contents of a session |
154 | | */ |
155 | 0 | static void oidc_session_clear(request_rec *r, oidc_session_t *z) { |
156 | 0 | z->remote_user = NULL; |
157 | | // NB: don't clear sid or uuid |
158 | 0 | z->expiry = 0; |
159 | 0 | if (z->state) { |
160 | 0 | oidc_json_decref(z->state); |
161 | 0 | z->state = NULL; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | /* |
166 | | * Replace the presented session on login. Reusing its id or contents would allow a planted |
167 | | * server-cache cookie to survive authentication; client-cookie sessions are re-keyed for |
168 | | * consistent behavior. |
169 | | */ |
170 | 0 | void oidc_session_reset(request_rec *r, const oidc_cfg_t *c, oidc_session_t *z) { |
171 | 0 | if ((oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) && (z->uuid != NULL)) |
172 | 0 | oidc_cache_set_session(r, z->uuid, NULL, 0); |
173 | 0 | oidc_session_clear(r, z); |
174 | | /* these two index the session for back-channel logout and belong to the previous occupant */ |
175 | 0 | z->sid = NULL; |
176 | 0 | z->sub = NULL; |
177 | 0 | oidc_session_id_new(r, z); |
178 | 0 | } |
179 | | |
180 | | /* |
181 | | * get a value from the session based on the name from a name/value pair |
182 | | */ |
183 | 0 | static apr_byte_t oidc_session_get(request_rec *r, const oidc_session_t *z, const char *key, char **value) { |
184 | | |
185 | | /* just return the value for the key */ |
186 | 0 | oidc_json_object_get_string(r->pool, z->state, key, value, NULL); |
187 | |
|
188 | 0 | return TRUE; |
189 | 0 | } |
190 | | |
191 | | /* |
192 | | * set a name/value key pair in the session |
193 | | */ |
194 | 0 | static apr_byte_t oidc_session_json_set(request_rec *r, oidc_session_t *z, const char *key, oidc_json_t *value) { |
195 | | |
196 | | /* only set it if non-NULL, otherwise delete the entry */ |
197 | 0 | if (value) { |
198 | 0 | if (z->state == NULL) |
199 | 0 | z->state = oidc_json_object(); |
200 | 0 | oidc_json_object_set_new(z->state, key, value); |
201 | 0 | } else if (z->state != NULL) { |
202 | 0 | oidc_json_object_del(z->state, key); |
203 | 0 | } |
204 | |
|
205 | 0 | return TRUE; |
206 | 0 | } |
207 | | |
208 | | /* |
209 | | * get a json object from the session based on the name from a name/value pair |
210 | | */ |
211 | 0 | static oidc_json_t *oidc_session_json_get(request_rec *r, const oidc_session_t *z, const char *key) { |
212 | 0 | return oidc_json_object_get(z->state, key); |
213 | 0 | } |
214 | | |
215 | | /* |
216 | | * set a name/string key pair in the session |
217 | | */ |
218 | 0 | static apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value) { |
219 | 0 | return oidc_session_json_set(r, z, key, value ? oidc_json_string(value) : NULL); |
220 | 0 | } |
221 | | |
222 | | /* |
223 | | * load the session from the session cache, indexed by its uuid session id |
224 | | */ |
225 | 0 | apr_byte_t oidc_session_load_cache_by_uuid(request_rec *r, const oidc_cfg_t *c, const char *uuid, oidc_session_t *z) { |
226 | 0 | char *stored_uuid = NULL; |
227 | 0 | char *s_json = NULL; |
228 | 0 | apr_byte_t rc = FALSE; |
229 | |
|
230 | 0 | rc = oidc_cache_get_session(r, uuid, &s_json); |
231 | |
|
232 | 0 | if ((rc == TRUE) && (s_json != NULL)) { |
233 | 0 | rc = oidc_session_decode(r, c, z, s_json, FALSE); |
234 | 0 | if (rc == TRUE) { |
235 | 0 | z->uuid = apr_pstrdup(r->pool, uuid); |
236 | | |
237 | | /* compare the session id in the cache value so it allows us to detect cache corruption */ |
238 | 0 | oidc_session_get(r, z, OIDC_SESSION_SESSION_ID, &stored_uuid); |
239 | 0 | if ((stored_uuid == NULL) || (_oidc_strcmp(stored_uuid, uuid) != 0)) { |
240 | 0 | oidc_error(r, |
241 | 0 | "cache corruption detected: stored session id (%s) is not equal to " |
242 | 0 | "requested session id (%s)", |
243 | 0 | oidc_session_id_fingerprint(r, stored_uuid), |
244 | 0 | oidc_session_id_fingerprint(r, uuid)); |
245 | | |
246 | | /* delete the cache entry */ |
247 | 0 | oidc_cache_set_session(r, z->uuid, NULL, 0); |
248 | | /* clear the session */ |
249 | 0 | oidc_session_clear(r, z); |
250 | |
|
251 | 0 | rc = FALSE; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | } |
255 | |
|
256 | 0 | return rc; |
257 | 0 | } |
258 | | |
259 | | /* |
260 | | * load the session from the cache using the cookie as the index |
261 | | */ |
262 | 0 | static apr_byte_t oidc_session_load_cache(request_rec *r, oidc_session_t *z) { |
263 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
264 | |
|
265 | 0 | apr_byte_t rc = FALSE; |
266 | | |
267 | | /* get the cookie that should be our uuid/key */ |
268 | 0 | const char *uuid = oidc_http_get_cookie(r, oidc_cfg_dir_cookie_get(r)); |
269 | | |
270 | | /* get the string-encoded session from the cache based on the key; decryption is based on the cache backend |
271 | | * config */ |
272 | 0 | if (uuid != NULL) { |
273 | |
|
274 | 0 | rc = oidc_session_load_cache_by_uuid(r, c, uuid, z); |
275 | | |
276 | | /* cache backend experienced an error while attempting lookup */ |
277 | 0 | if (rc == FALSE) { |
278 | 0 | oidc_error(r, "cache backend failure for key %s", oidc_session_id_fingerprint(r, uuid)); |
279 | 0 | return FALSE; |
280 | 0 | } |
281 | | |
282 | | /* cache backend does not contain an entry for the given key */ |
283 | 0 | if (z->state == NULL) { |
284 | | |
285 | | /* |
286 | | * With OIDCSessionCacheFallbackToCookie this may be a complete session, not a cache key. |
287 | | * Preserve it and report failure so oidc_session_load() can try the cookie fallback. |
288 | | */ |
289 | 0 | if (oidc_cfg_session_cache_fallback_to_cookie_get(c)) |
290 | 0 | return FALSE; |
291 | | |
292 | | /* delete the session cookie */ |
293 | 0 | oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), "", 0, |
294 | 0 | OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | 0 | return rc; |
299 | 0 | } |
300 | | |
301 | 0 | static const char *oidc_session_cookie_samesite(const request_rec *r, const struct oidc_cfg_t *c, int first_time) { |
302 | 0 | const char *rv = NULL; |
303 | 0 | switch (oidc_cfg_cookie_same_site_session_get(c)) { |
304 | 0 | case OIDC_SAMESITE_COOKIE_STRICT: |
305 | 0 | rv = first_time ? OIDC_HTTP_COOKIE_SAMESITE_LAX : OIDC_HTTP_COOKIE_SAMESITE_STRICT; |
306 | 0 | break; |
307 | 0 | case OIDC_SAMESITE_COOKIE_LAX: |
308 | 0 | rv = OIDC_HTTP_COOKIE_SAMESITE_LAX; |
309 | 0 | break; |
310 | 0 | case OIDC_SAMESITE_COOKIE_NONE: |
311 | 0 | rv = OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r); |
312 | 0 | break; |
313 | 0 | case OIDC_SAMESITE_COOKIE_DISABLED: |
314 | 0 | break; |
315 | 0 | default: |
316 | 0 | break; |
317 | 0 | } |
318 | 0 | return rv; |
319 | 0 | } |
320 | | |
321 | | /* |
322 | | * save the session to the cache using a cookie for the index |
323 | | */ |
324 | 0 | static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z, oidc_session_save_t first_time) { |
325 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
326 | |
|
327 | 0 | apr_byte_t rc = TRUE; |
328 | |
|
329 | 0 | if (z->state != NULL) { |
330 | |
|
331 | 0 | if (z->sid != NULL) { |
332 | 0 | oidc_cache_set_sid(r, z->sid, z->uuid, z->expiry); |
333 | 0 | oidc_session_set(r, z, OIDC_SESSION_SID_KEY, z->sid); |
334 | 0 | } |
335 | | |
336 | | /* secondary "sub"-based logout index (shares the sid cache namespace, distinct key) */ |
337 | 0 | if (z->sub != NULL) { |
338 | 0 | oidc_cache_set_sid(r, z->sub, z->uuid, z->expiry); |
339 | 0 | oidc_session_set(r, z, OIDC_SESSION_SUB_KEY, z->sub); |
340 | 0 | } |
341 | | |
342 | | /* store the string-encoded session in the cache; encryption depends on cache backend settings */ |
343 | 0 | char *s_value = NULL; |
344 | 0 | if (oidc_session_encode(r, c, z, &s_value, FALSE) == FALSE) |
345 | 0 | return FALSE; |
346 | | |
347 | 0 | rc = oidc_cache_set_session(r, z->uuid, s_value, z->expiry); |
348 | 0 | if (rc == TRUE) |
349 | | /* set the uuid in the cookie */ |
350 | 0 | oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), z->uuid, |
351 | 0 | oidc_cfg_persistent_session_cookie_get(c) ? z->expiry : -1, |
352 | 0 | oidc_session_cookie_samesite(r, c, first_time)); |
353 | |
|
354 | 0 | } else { |
355 | |
|
356 | 0 | if (z->sid != NULL) |
357 | 0 | oidc_cache_set_sid(r, z->sid, NULL, 0); |
358 | 0 | if (z->sub != NULL) { |
359 | | /* Delete the shared sub index only when it still points to this session. */ |
360 | 0 | char *sub_uuid = NULL; |
361 | 0 | oidc_cache_get_sid(r, z->sub, &sub_uuid); |
362 | 0 | if ((sub_uuid == NULL) || (_oidc_strcmp(sub_uuid, z->uuid) == 0)) |
363 | 0 | oidc_cache_set_sid(r, z->sub, NULL, 0); |
364 | 0 | } |
365 | | |
366 | | /* clear the cookie */ |
367 | 0 | oidc_http_set_cookie(r, oidc_cfg_dir_cookie_get(r), "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r)); |
368 | | |
369 | | /* remove the session from the cache */ |
370 | 0 | rc = oidc_cache_set_session(r, z->uuid, NULL, 0); |
371 | 0 | } |
372 | | |
373 | 0 | return rc; |
374 | 0 | } |
375 | | |
376 | | /* |
377 | | * load the session from a self-contained client-side cookie |
378 | | */ |
379 | 0 | static apr_byte_t oidc_session_load_cookie(request_rec *r, const oidc_cfg_t *c, oidc_session_t *z) { |
380 | 0 | const char *cookieValue = |
381 | 0 | oidc_http_get_chunked_cookie(r, oidc_cfg_dir_cookie_get(r), oidc_cfg_session_cookie_chunk_size_get(c)); |
382 | |
|
383 | 0 | if (cookieValue == NULL) |
384 | 0 | return TRUE; |
385 | | |
386 | 0 | if (oidc_session_decode(r, c, z, cookieValue, TRUE) == FALSE) |
387 | 0 | return FALSE; |
388 | | |
389 | 0 | return TRUE; |
390 | 0 | } |
391 | | |
392 | | /* |
393 | | * store the session in a self-contained client-side-only cookie storage |
394 | | */ |
395 | 0 | static apr_byte_t oidc_session_save_cookie(request_rec *r, const oidc_session_t *z, oidc_session_save_t first_time) { |
396 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
397 | 0 | char *cookieValue = ""; |
398 | 0 | if ((z->state != NULL) && (oidc_session_encode(r, c, z, &cookieValue, TRUE) == FALSE)) |
399 | 0 | return FALSE; |
400 | | |
401 | | /* Reject sessions that exceed the cookie-chunk limit to avoid an authentication loop. */ |
402 | 0 | return oidc_http_set_chunked_cookie( |
403 | 0 | r, oidc_cfg_dir_cookie_get(r), cookieValue, oidc_cfg_persistent_session_cookie_get(c) ? z->expiry : -1, |
404 | 0 | oidc_cfg_session_cookie_chunk_size_get(c), |
405 | 0 | (z->state == NULL) ? OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r) : oidc_session_cookie_samesite(r, c, first_time)); |
406 | 0 | } |
407 | | |
408 | | /* |
409 | | * retrieve an integer from the session state |
410 | | */ |
411 | 0 | static inline int oidc_session_get_int(request_rec *r, const oidc_session_t *z, const char *key, int def_val) { |
412 | 0 | int v; |
413 | 0 | oidc_json_object_get_int(z->state, key, &v, def_val); |
414 | 0 | return v; |
415 | 0 | } |
416 | | |
417 | | /* |
418 | | * retrieve a timestamp from the session state |
419 | | */ |
420 | 0 | static inline apr_time_t oidc_session_get_key2timestamp(request_rec *r, const oidc_session_t *z, const char *key) { |
421 | 0 | oidc_json_int_t value = -1; |
422 | 0 | oidc_json_object_get_int64(z->state, key, &value, -1); |
423 | | /* saturate rather than wrap, guarding a tampered client-cookie session with an oversized value */ |
424 | 0 | return (value > -1) ? oidc_util_apr_time_from_sec((double)value) : -1; |
425 | 0 | } |
426 | | |
427 | | /* |
428 | | * parse data from the session state into the session struct members |
429 | | */ |
430 | 0 | apr_byte_t oidc_session_extract(request_rec *r, oidc_session_t *z) { |
431 | 0 | apr_byte_t rc = FALSE; |
432 | 0 | const oidc_json_t *json = NULL; |
433 | |
|
434 | 0 | if (z->state == NULL) |
435 | 0 | goto out; |
436 | | |
437 | | /* sanity check also taking into account OIDC_DONT_STORE_ID_TOKEN_CLAIMS_IN_SESSION=true */ |
438 | 0 | json = oidc_session_json_get(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS); |
439 | 0 | if ((json != NULL) && (!oidc_json_is_object(json))) { |
440 | |
|
441 | 0 | oidc_error(r, "session is corrupted/incompatible: id_token claims is not a JSON object"); |
442 | 0 | oidc_session_kill(r, z); |
443 | |
|
444 | 0 | goto out; |
445 | 0 | } |
446 | | |
447 | | /* check whether it has expired */ |
448 | 0 | z->expiry = oidc_session_get_key2timestamp(r, z, OIDC_SESSION_EXPIRY_KEY); |
449 | 0 | if (apr_time_now() > z->expiry) { |
450 | |
|
451 | 0 | oidc_warn(r, "session restored from cache has expired"); |
452 | 0 | oidc_session_kill(r, z); |
453 | |
|
454 | 0 | goto out; |
455 | 0 | } |
456 | | |
457 | 0 | oidc_session_get(r, z, OIDC_SESSION_REMOTE_USER_KEY, &z->remote_user); |
458 | 0 | oidc_session_get(r, z, OIDC_SESSION_SID_KEY, &z->sid); |
459 | 0 | oidc_session_get(r, z, OIDC_SESSION_SUB_KEY, &z->sub); |
460 | 0 | oidc_session_get(r, z, OIDC_SESSION_SESSION_ID, &z->uuid); |
461 | |
|
462 | 0 | rc = TRUE; |
463 | |
|
464 | 0 | out: |
465 | |
|
466 | 0 | return rc; |
467 | 0 | } |
468 | | |
469 | 0 | #define OIDC_SESSION_ID_LOG_VAR "OIDC_SESSION_ID" |
470 | 0 | #define OIDC_SESSION_ID_FINGERPRINT_LEN 16 |
471 | | |
472 | | /* |
473 | | * non-reversible fingerprint of a session identifier, for the OIDC_SESSION_ID export and for log |
474 | | * lines: with server-side caching the identifier is the session cookie value itself, so the raw |
475 | | * value must never reach a log where it could be replayed as that cookie; the first 16 characters |
476 | | * (96 bits) of the base64url-encoded SHA-256 hash correlate just as well |
477 | | */ |
478 | 0 | const char *oidc_session_id_fingerprint(request_rec *r, const char *uuid) { |
479 | 0 | char *hash = NULL; |
480 | 0 | if (uuid == NULL) |
481 | 0 | return NULL; |
482 | 0 | if ((oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, uuid, &hash) == FALSE) || |
483 | 0 | (hash == NULL)) |
484 | | /* never fall back to the identifier itself */ |
485 | 0 | return "***"; |
486 | 0 | return apr_pstrndup(r->pool, hash, OIDC_SESSION_ID_FINGERPRINT_LEN); |
487 | 0 | } |
488 | | |
489 | | /* |
490 | | * export a fingerprint of the session identifier into the request notes and environment so log |
491 | | * lines can be correlated per session with ErrorLogFormat %{OIDC_SESSION_ID}n and LogFormat |
492 | | * %{OIDC_SESSION_ID}e |
493 | | */ |
494 | 0 | static void oidc_session_id_export(request_rec *r, const oidc_session_t *z) { |
495 | 0 | const char *fingerprint = oidc_session_id_fingerprint(r, z->uuid); |
496 | 0 | if (fingerprint == NULL) |
497 | 0 | return; |
498 | 0 | if (r->notes != NULL) |
499 | 0 | apr_table_set(r->notes, OIDC_SESSION_ID_LOG_VAR, fingerprint); |
500 | 0 | if (r->subprocess_env != NULL) |
501 | 0 | apr_table_set(r->subprocess_env, OIDC_SESSION_ID_LOG_VAR, fingerprint); |
502 | 0 | } |
503 | | |
504 | | /* |
505 | | * load a session from the cache/cookie |
506 | | */ |
507 | 0 | apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **zz) { |
508 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
509 | |
|
510 | 0 | apr_byte_t rc = FALSE; |
511 | | |
512 | | /* allocate space for the session object and fill it */ |
513 | 0 | oidc_session_t *z = (*zz = apr_pcalloc(r->pool, sizeof(oidc_session_t))); |
514 | 0 | oidc_session_clear(r, z); |
515 | 0 | oidc_session_id_new(r, z); |
516 | 0 | z->sid = NULL; |
517 | 0 | z->sub = NULL; |
518 | |
|
519 | 0 | if (oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) |
520 | | /* load the session from the cache */ |
521 | 0 | rc = oidc_session_load_cache(r, z); |
522 | | |
523 | | /* if we get here we configured client-cookie or retrieving from the cache failed */ |
524 | 0 | if ((oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_CLIENT_COOKIE) || |
525 | 0 | ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie_get(c))) |
526 | | /* load the session from a self-contained cookie */ |
527 | 0 | rc = oidc_session_load_cookie(r, c, z); |
528 | |
|
529 | 0 | if (rc == TRUE) { |
530 | 0 | rc = oidc_session_extract(r, z); |
531 | 0 | if (rc == TRUE) |
532 | 0 | oidc_session_id_export(r, z); |
533 | 0 | } |
534 | |
|
535 | 0 | oidc_util_set_trace_parent(r, c, z->uuid); |
536 | |
|
537 | 0 | return rc; |
538 | 0 | } |
539 | | |
540 | | /* |
541 | | * store an integer value into the session state |
542 | | */ |
543 | 0 | static void oidc_session_set_int(request_rec *r, oidc_session_t *z, const char *key, int v) { |
544 | 0 | if (z->state == NULL) |
545 | 0 | z->state = oidc_json_object(); |
546 | 0 | oidc_json_object_set_new(z->state, key, oidc_json_integer(v)); |
547 | 0 | } |
548 | | |
549 | | /* |
550 | | * store a timestamp value into the session state |
551 | | */ |
552 | 0 | static void oidc_session_set_timestamp(request_rec *r, oidc_session_t *z, const char *key, const apr_time_t timestamp) { |
553 | 0 | if (timestamp <= -1) |
554 | 0 | return; |
555 | 0 | if (z->state == NULL) |
556 | 0 | z->state = oidc_json_object(); |
557 | | /* store the full seconds value as a 64-bit JSON integer: the previous (int) cast wrapped any |
558 | | * timestamp past January 2038 into a negative, i.e. already-expired, value */ |
559 | 0 | oidc_json_object_set_new(z->state, key, oidc_json_integer(apr_time_sec(timestamp))); |
560 | 0 | } |
561 | | |
562 | | /* |
563 | | * save a session to cache/cookie |
564 | | */ |
565 | 0 | apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, oidc_session_save_t first_time) { |
566 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
567 | |
|
568 | 0 | apr_byte_t rc = FALSE; |
569 | |
|
570 | 0 | if (z->state != NULL) { |
571 | 0 | oidc_session_set_int(r, z, OIDC_SESSION_FORMAT_VERSION_KEY, OIDC_SESSION_FORMAT_VERSION); |
572 | 0 | oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user); |
573 | 0 | oidc_session_set_timestamp(r, z, OIDC_SESSION_EXPIRY_KEY, z->expiry); |
574 | 0 | oidc_session_set(r, z, OIDC_SESSION_SESSION_ID, z->uuid); |
575 | 0 | } |
576 | |
|
577 | 0 | if (oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_SERVER_CACHE) |
578 | | /* store the session in the cache */ |
579 | 0 | rc = oidc_session_save_cache(r, z, first_time); |
580 | | |
581 | | /* if we get here we configured client-cookie or saving in the cache failed */ |
582 | 0 | if ((oidc_cfg_session_type_get(c) == OIDC_SESSION_TYPE_CLIENT_COOKIE) || |
583 | 0 | ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie_get(c))) { |
584 | 0 | if (oidc_cfg_session_type_get(c) != OIDC_SESSION_TYPE_CLIENT_COOKIE) |
585 | 0 | OIDC_METRICS_COUNTER_INC(r, c, OM_SESSION_FALLBACK_COOKIE); |
586 | | /* store the session in a self-contained cookie */ |
587 | 0 | rc = oidc_session_save_cookie(r, z, first_time); |
588 | 0 | } |
589 | |
|
590 | 0 | if (rc == TRUE) |
591 | 0 | oidc_session_id_export(r, z); |
592 | |
|
593 | 0 | return rc; |
594 | 0 | } |
595 | | |
596 | | /* |
597 | | * free resources allocated for a session |
598 | | */ |
599 | 0 | apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z) { |
600 | 0 | oidc_session_clear(r, z); |
601 | 0 | return TRUE; |
602 | 0 | } |
603 | | |
604 | | /* |
605 | | * terminate a session |
606 | | */ |
607 | 0 | apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z) { |
608 | 0 | r->user = NULL; |
609 | 0 | if (z->state) { |
610 | 0 | oidc_json_decref(z->state); |
611 | 0 | z->state = NULL; |
612 | 0 | } |
613 | 0 | oidc_session_save(r, z, OIDC_SESSION_SAVE_UPDATE); |
614 | 0 | return oidc_session_free(r, z); |
615 | 0 | } |
616 | | |
617 | | /* |
618 | | * session object keys |
619 | | */ |
620 | | /* key for storing the userinfo JWT in the session context */ |
621 | 0 | #define OIDC_SESSION_KEY_USERINFO_JWT "uij" |
622 | | /* key for storing the raw id_token in the session context */ |
623 | 0 | #define OIDC_SESSION_KEY_IDTOKEN "idt" |
624 | | /* key for storing the access_token in the session context */ |
625 | 0 | #define OIDC_SESSION_KEY_ACCESSTOKEN "at" |
626 | | /* key for storing the access_token type in the session context */ |
627 | 0 | #define OIDC_SESSION_KEY_ACCESSTOKEN_TYPE "att" |
628 | | /* key for storing the access_token expiry in the session context */ |
629 | 0 | #define OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES "ate" |
630 | | /* key for storing the refresh_token in the session context */ |
631 | 0 | #define OIDC_SESSION_KEY_REFRESH_TOKEN "rt" |
632 | | /* key for storing maximum session duration in the session context */ |
633 | 0 | #define OIDC_SESSION_KEY_SESSION_EXPIRES "se" |
634 | | /* key for storing the cookie domain in the session context */ |
635 | 0 | #define OIDC_SESSION_KEY_COOKIE_DOMAIN "cd" |
636 | | /* key for storing last user info refresh timestamp in the session context */ |
637 | 0 | #define OIDC_SESSION_KEY_USERINFO_LAST_REFRESH "uilr" |
638 | | /* key for storing last access token refresh timestamp in the session context */ |
639 | 0 | #define OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH "atlr" |
640 | | /* key for storing request state */ |
641 | 0 | #define OIDC_SESSION_KEY_REQUEST_STATE "rs" |
642 | | /* key for storing the original URL */ |
643 | 0 | #define OIDC_SESSION_KEY_ORIGINAL_URL "ou" |
644 | | /* key for storing the session_state in the session context */ |
645 | 0 | #define OIDC_SESSION_KEY_SESSION_STATE "ss" |
646 | | /* key for storing the issuer in the session context */ |
647 | 0 | #define OIDC_SESSION_KEY_ISSUER "iss" |
648 | | /* key for storing the provider specific user info refresh interval */ |
649 | 0 | #define OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL "uir" |
650 | | /* key for storing whether this is a newly created session or not */ |
651 | 0 | #define OIDC_SESSION_KEY_SESSION_IS_NEW "sn" |
652 | | /* key for storing the scope in the session context */ |
653 | 0 | #define OIDC_SESSION_KEY_SCOPE "scp" |
654 | | |
655 | 0 | #define OIDC_SESSION_KEY_PATH_AUTH_REQUEST_PARAMS "parp" |
656 | | |
657 | 0 | #define OIDC_SESSION_KEY_PATH_SCOPE "psc" |
658 | | |
659 | | /* |
660 | | * helper functions |
661 | | */ |
662 | 0 | static const char *oidc_session_get_key2string(request_rec *r, const oidc_session_t *z, const char *key) { |
663 | 0 | char *s_value = NULL; |
664 | 0 | oidc_session_get(r, z, key, &s_value); |
665 | 0 | return s_value; |
666 | 0 | } |
667 | | |
668 | 0 | #define OIDC_SESSION_WARN_CLAIM_SIZE (1024 * 8) |
669 | 0 | #define OIDC_SESSION_WARN_CLAIM_SIZE_VAR "OIDC_SESSION_WARN_CLAIM_SIZE" |
670 | | |
671 | | /* |
672 | | * read the warning threshold for the (encoded) size of a single session claim from the environment |
673 | | */ |
674 | 0 | static int oidc_session_warn_claim_size_get(request_rec *r) { |
675 | 0 | const char *str = NULL; |
676 | |
|
677 | 0 | if (r->subprocess_env == NULL) |
678 | 0 | return OIDC_SESSION_WARN_CLAIM_SIZE; |
679 | | |
680 | 0 | str = apr_table_get(r->subprocess_env, OIDC_SESSION_WARN_CLAIM_SIZE_VAR); |
681 | 0 | if (str == NULL) |
682 | 0 | return OIDC_SESSION_WARN_CLAIM_SIZE; |
683 | | |
684 | 0 | int warn_claim_size = _oidc_str_to_int(str, OIDC_SESSION_WARN_CLAIM_SIZE); |
685 | 0 | oidc_debug(r, "warn_claim_size set to %d in environment variable %s", warn_claim_size, |
686 | 0 | OIDC_SESSION_WARN_CLAIM_SIZE_VAR); |
687 | 0 | return warn_claim_size; |
688 | 0 | } |
689 | | |
690 | | /* |
691 | | * check whether a single claim passes the configured black/white list filters |
692 | | */ |
693 | | static apr_byte_t oidc_session_claim_is_allowed(request_rec *r, const oidc_cfg_t *c, const char *session_key, |
694 | 0 | const char *name) { |
695 | 0 | if ((oidc_cfg_black_listed_claims_get(c) != NULL) && |
696 | 0 | (apr_hash_get(oidc_cfg_black_listed_claims_get(c), name, APR_HASH_KEY_STRING) != NULL)) { |
697 | 0 | oidc_debug(r, "removing blacklisted claim [%s]: '%s'", session_key, name); |
698 | 0 | return FALSE; |
699 | 0 | } |
700 | | |
701 | 0 | if ((oidc_cfg_white_listed_claims_get(c) != NULL) && |
702 | 0 | (apr_hash_get(oidc_cfg_white_listed_claims_get(c), name, APR_HASH_KEY_STRING) == NULL)) { |
703 | 0 | oidc_debug(r, "removing non-whitelisted claim [%s]: '%s'", session_key, name); |
704 | 0 | return FALSE; |
705 | 0 | } |
706 | | |
707 | 0 | return TRUE; |
708 | 0 | } |
709 | | |
710 | | /* |
711 | | * record a single allowed claim into the destination object, increment metrics and warn on oversized values |
712 | | */ |
713 | | static void oidc_session_filtered_claim_record(request_rec *r, const oidc_cfg_t *c, const char *session_key, |
714 | | const char *name, oidc_json_t *value, oidc_json_t *dst, |
715 | 0 | int warn_claim_size) { |
716 | 0 | const char *str = value ? oidc_json_encode(r->pool, value, |
717 | 0 | OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT | OIDC_JSON_ENCODE_ANY) |
718 | 0 | : ""; |
719 | |
|
720 | 0 | if ((int)_oidc_strlen(str) > warn_claim_size) |
721 | 0 | oidc_warn(r, |
722 | 0 | "(encoded) value size of [%s] claim \"%s\" is larger than %d; consider " |
723 | 0 | "blacklisting it in OIDCBlackListedClaims " |
724 | 0 | "or increase the warning limit with environment variable %s", |
725 | 0 | session_key, name, warn_claim_size, OIDC_SESSION_WARN_CLAIM_SIZE_VAR); |
726 | |
|
727 | 0 | oidc_json_object_set(dst, name, value); |
728 | |
|
729 | 0 | const char *metric_value = oidc_json_is_string(value) ? oidc_json_string_value(value) : str; |
730 | 0 | if (_oidc_strcmp(session_key, OIDC_SESSION_KEY_USERINFO_CLAIMS) == 0) { |
731 | 0 | OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, c, OM_CLAIM_USER_INFO, name, metric_value); |
732 | 0 | } else { |
733 | 0 | OIDC_METRICS_COUNTER_INC_NAME_VALUE(r, c, OM_CLAIM_ID_TOKEN, name, metric_value); |
734 | 0 | } |
735 | 0 | } |
736 | | |
737 | | #ifdef USE_LIBJQ |
738 | | /* |
739 | | * apply the configured JQ filter to the collected claims; replaces *dst with the filtered object on success |
740 | | */ |
741 | | static void oidc_session_jq_filter_apply(request_rec *r, const oidc_cfg_t *c, const char *session_key, |
742 | | oidc_json_t **dst) { |
743 | | const oidc_apr_expr_t *filter = oidc_cfg_filter_claims_expr_get(c); |
744 | | const char *s_filter = oidc_util_apr_expr_exec(r, filter, OIDC_APR_EXPR_RESULT_STRING); |
745 | | |
746 | | if (filter == NULL) |
747 | | return; |
748 | | |
749 | | const char *filtered_claims = oidc_util_jq_filter(r, *dst, s_filter); |
750 | | oidc_json_decref(*dst); |
751 | | *dst = NULL; |
752 | | if (oidc_json_decode_object(r, filtered_claims, dst) == FALSE) |
753 | | oidc_error(r, "JQ filtering of claims for [%s] resulted in invalid JSON object, filter='%s'", |
754 | | session_key, s_filter); |
755 | | } |
756 | | #endif |
757 | | |
758 | | /* |
759 | | * apply whitelisting/blacklisting and a JQ filter to the provided (serialized JSON) claims |
760 | | * session_key may refer to id_token claims or userinfo claims |
761 | | */ |
762 | | static void oidc_session_set_filtered_claims(request_rec *r, oidc_session_t *z, const char *session_key, |
763 | 0 | oidc_json_t *claims) { |
764 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
765 | 0 | oidc_json_t *dst = NULL; |
766 | 0 | void *iter = NULL; |
767 | 0 | int warn_claim_size = oidc_session_warn_claim_size_get(r); |
768 | | |
769 | | // avoid gcc 14 warning: '%s' directive argument is null [-Wformat-overflow=] |
770 | 0 | if (session_key == NULL) |
771 | 0 | session_key = ""; |
772 | |
|
773 | 0 | if (claims != NULL) { |
774 | 0 | dst = oidc_json_object(); |
775 | 0 | iter = oidc_json_object_iter(claims); |
776 | 0 | } |
777 | | |
778 | | // NB: need this loop for all claims for the metrics |
779 | 0 | while (iter) { |
780 | 0 | const char *name = oidc_json_object_iter_key(iter); |
781 | 0 | oidc_json_t *value = oidc_json_object_iter_value(iter); |
782 | |
|
783 | 0 | if (oidc_session_claim_is_allowed(r, c, session_key, name) == TRUE) |
784 | 0 | oidc_session_filtered_claim_record(r, c, session_key, name, value, dst, warn_claim_size); |
785 | |
|
786 | 0 | iter = oidc_json_object_iter_next(claims, iter); |
787 | 0 | } |
788 | |
|
789 | | #ifdef USE_LIBJQ |
790 | | oidc_session_jq_filter_apply(r, c, session_key, &dst); |
791 | | #endif |
792 | |
|
793 | 0 | oidc_session_json_set(r, z, session_key, dst); |
794 | 0 | } |
795 | | |
796 | | /* |
797 | | * userinfo claims |
798 | | */ |
799 | 0 | void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, oidc_json_t *claims_json) { |
800 | 0 | oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS, claims_json); |
801 | 0 | } |
802 | | |
803 | 0 | oidc_json_t *oidc_session_get_userinfo_claims(request_rec *r, const oidc_session_t *z) { |
804 | 0 | return oidc_session_json_get(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS); |
805 | 0 | } |
806 | | |
807 | 0 | void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *s_userinfo_jwt) { |
808 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_USERINFO_JWT, s_userinfo_jwt); |
809 | 0 | } |
810 | | |
811 | 0 | const char *oidc_session_get_userinfo_jwt(request_rec *r, const oidc_session_t *z) { |
812 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_JWT); |
813 | 0 | } |
814 | | |
815 | | /* |
816 | | * id_token claims |
817 | | */ |
818 | 0 | void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, oidc_json_t *idtoken_claims) { |
819 | 0 | if (apr_table_get(r->subprocess_env, "OIDC_DONT_STORE_ID_TOKEN_CLAIMS_IN_SESSION") == NULL) |
820 | 0 | oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS, idtoken_claims); |
821 | 0 | } |
822 | | |
823 | 0 | oidc_json_t *oidc_session_get_idtoken_claims(request_rec *r, const oidc_session_t *z) { |
824 | 0 | return oidc_session_json_get(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS); |
825 | 0 | } |
826 | | |
827 | | /* |
828 | | * compact serialized id_token |
829 | | */ |
830 | 0 | void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token) { |
831 | 0 | oidc_debug(r, "storing id_token in the session"); |
832 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_IDTOKEN, s_id_token); |
833 | 0 | } |
834 | | |
835 | 0 | const char *oidc_session_get_idtoken(request_rec *r, const oidc_session_t *z) { |
836 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN); |
837 | 0 | } |
838 | | |
839 | | /* |
840 | | * access token |
841 | | */ |
842 | 0 | void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token) { |
843 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN, access_token); |
844 | 0 | } |
845 | | |
846 | 0 | const char *oidc_session_get_access_token(request_rec *r, const oidc_session_t *z) { |
847 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN); |
848 | 0 | } |
849 | | |
850 | | /* |
851 | | * access token type |
852 | | */ |
853 | 0 | void oidc_session_set_access_token_type(request_rec *r, oidc_session_t *z, const char *token_type) { |
854 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE, token_type); |
855 | 0 | } |
856 | | |
857 | 0 | const char *oidc_session_get_access_token_type(request_rec *r, const oidc_session_t *z) { |
858 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE); |
859 | 0 | } |
860 | | |
861 | | /* |
862 | | * access token expires |
863 | | */ |
864 | 0 | void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in) { |
865 | 0 | if (expires_in > -1) { |
866 | 0 | oidc_debug(r, "storing access token expires_in in the session: %d", expires_in); |
867 | 0 | oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES, |
868 | 0 | apr_time_now() + apr_time_from_sec(expires_in)); |
869 | 0 | } |
870 | 0 | } |
871 | | |
872 | 0 | apr_time_t oidc_session_get_access_token_expires(request_rec *r, const oidc_session_t *z) { |
873 | 0 | return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES); |
874 | 0 | } |
875 | | |
876 | 0 | const char *oidc_session_get_access_token_expires2str(request_rec *r, const oidc_session_t *z) { |
877 | 0 | apr_time_t expires = oidc_session_get_access_token_expires(r, z); |
878 | 0 | return (expires > -1) ? apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(expires)) : NULL; |
879 | 0 | } |
880 | | |
881 | | /* |
882 | | * refresh token |
883 | | */ |
884 | 0 | void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token) { |
885 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN, refresh_token); |
886 | 0 | } |
887 | | |
888 | 0 | const char *oidc_session_get_refresh_token(request_rec *r, const oidc_session_t *z) { |
889 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN); |
890 | 0 | } |
891 | | |
892 | | /* |
893 | | * session expires |
894 | | */ |
895 | 0 | void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires) { |
896 | 0 | oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES, expires); |
897 | 0 | } |
898 | | |
899 | 0 | apr_time_t oidc_session_get_session_expires(request_rec *r, const oidc_session_t *z) { |
900 | 0 | return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES); |
901 | 0 | } |
902 | | |
903 | | /* |
904 | | * cookie domain |
905 | | */ |
906 | 0 | void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain) { |
907 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN, cookie_domain); |
908 | 0 | } |
909 | | |
910 | 0 | const char *oidc_session_get_cookie_domain(request_rec *r, const oidc_session_t *z) { |
911 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN); |
912 | 0 | } |
913 | | |
914 | | /* |
915 | | * userinfo last refresh |
916 | | */ |
917 | | |
918 | 0 | void oidc_session_set_userinfo_refresh_interval(request_rec *r, oidc_session_t *z, const int interval) { |
919 | 0 | oidc_session_set_int(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL, interval); |
920 | 0 | } |
921 | | |
922 | 0 | int oidc_session_get_userinfo_refresh_interval(request_rec *r, const oidc_session_t *z) { |
923 | 0 | return oidc_session_get_int(r, z, OIDC_SESSION_KEY_USERINFO_REFRESH_INTERVAL, -1); |
924 | 0 | } |
925 | | |
926 | 0 | void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z) { |
927 | 0 | oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH, apr_time_now()); |
928 | 0 | } |
929 | | |
930 | 0 | apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, const oidc_session_t *z) { |
931 | 0 | return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH); |
932 | 0 | } |
933 | | |
934 | | /* |
935 | | * access_token last refresh |
936 | | */ |
937 | 0 | void oidc_session_set_access_token_last_refresh(request_rec *r, oidc_session_t *z, apr_time_t ts) { |
938 | 0 | oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH, ts); |
939 | 0 | } |
940 | | |
941 | 0 | apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, const oidc_session_t *z) { |
942 | 0 | return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH); |
943 | 0 | } |
944 | | |
945 | | /* |
946 | | * request state |
947 | | */ |
948 | 0 | void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state) { |
949 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_REQUEST_STATE, request_state); |
950 | 0 | } |
951 | | |
952 | 0 | const char *oidc_session_get_request_state(request_rec *r, const oidc_session_t *z) { |
953 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REQUEST_STATE); |
954 | 0 | } |
955 | | |
956 | | /* |
957 | | * original url |
958 | | */ |
959 | 0 | void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url) { |
960 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_ORIGINAL_URL, original_url); |
961 | 0 | } |
962 | | |
963 | 0 | const char *oidc_session_get_original_url(request_rec *r, const oidc_session_t *z) { |
964 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ORIGINAL_URL); |
965 | 0 | } |
966 | | |
967 | | /* |
968 | | * session state |
969 | | */ |
970 | 0 | void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state) { |
971 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_SESSION_STATE, session_state); |
972 | 0 | } |
973 | | |
974 | 0 | const char *oidc_session_get_session_state(request_rec *r, const oidc_session_t *z) { |
975 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_SESSION_STATE); |
976 | 0 | } |
977 | | |
978 | | /* |
979 | | * issuer |
980 | | */ |
981 | 0 | void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer) { |
982 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_ISSUER, issuer); |
983 | 0 | } |
984 | | |
985 | 0 | const char *oidc_session_get_issuer(request_rec *r, const oidc_session_t *z) { |
986 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ISSUER); |
987 | 0 | } |
988 | | |
989 | | /* |
990 | | * new session |
991 | | */ |
992 | 0 | void oidc_session_set_session_new(request_rec *r, oidc_session_t *z, const int is_new) { |
993 | 0 | if (z->state == NULL) |
994 | 0 | z->state = oidc_json_object(); |
995 | 0 | if (is_new) |
996 | 0 | oidc_json_object_set_new(z->state, OIDC_SESSION_KEY_SESSION_IS_NEW, oidc_json_integer(1)); |
997 | 0 | else |
998 | 0 | oidc_json_object_del(z->state, OIDC_SESSION_KEY_SESSION_IS_NEW); |
999 | 0 | } |
1000 | | |
1001 | 0 | int oidc_session_get_session_new(request_rec *r, const oidc_session_t *z) { |
1002 | 0 | return oidc_session_get_int(r, z, OIDC_SESSION_KEY_SESSION_IS_NEW, 0); |
1003 | 0 | } |
1004 | | |
1005 | | /* |
1006 | | * scope |
1007 | | */ |
1008 | 0 | void oidc_session_set_scope(request_rec *r, oidc_session_t *z, const char *scope) { |
1009 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_SCOPE, scope); |
1010 | 0 | } |
1011 | | |
1012 | 0 | const char *oidc_session_get_scope(request_rec *r, const oidc_session_t *z) { |
1013 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_SCOPE); |
1014 | 0 | } |
1015 | | |
1016 | 0 | void oidc_session_set_path_auth_request_params(request_rec *r, oidc_session_t *z, const char *auth_request_params) { |
1017 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_PATH_AUTH_REQUEST_PARAMS, auth_request_params); |
1018 | 0 | } |
1019 | | |
1020 | 0 | const char *oidc_session_get_path_auth_request_params(request_rec *r, const oidc_session_t *z) { |
1021 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_PATH_AUTH_REQUEST_PARAMS); |
1022 | 0 | } |
1023 | | |
1024 | 0 | void oidc_session_set_path_scope(request_rec *r, oidc_session_t *z, const char *path_scope) { |
1025 | 0 | oidc_session_set(r, z, OIDC_SESSION_KEY_PATH_SCOPE, path_scope); |
1026 | 0 | } |
1027 | | |
1028 | 0 | const char *oidc_session_get_path_scope(request_rec *r, const oidc_session_t *z) { |
1029 | 0 | return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_PATH_SCOPE); |
1030 | 0 | } |