Coverage Report

Created: 2026-08-31 07:13

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