Coverage Report

Created: 2026-09-27 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/mod_auth_openidc.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
 * Initially based on mod_auth_cas.c:
42
 * https://github.com/Jasig/mod_auth_cas
43
 *
44
 * Other code copied/borrowed/adapted:
45
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
46
 *
47
 **************************************************************************/
48
49
#include "mod_auth_openidc.h"
50
#include "cfg/cache.h"
51
#include "cfg/check.h"
52
#include "cfg/dir.h"
53
#include "cfg/oauth.h"
54
#include "handle/handle.h"
55
#include "metadata.h"
56
#include "metrics.h"
57
#include "oauth.h"
58
#include "proto/proto.h"
59
#include "util/request_state.h"
60
#include "util/util.h"
61
#include "util/util_cfg.h"
62
63
#define OPENSSL_THREAD_DEFINES
64
#include <openssl/err.h>
65
#include <openssl/evp.h>
66
#include <openssl/opensslconf.h>
67
#include <openssl/opensslv.h>
68
#if (OPENSSL_VERSION_NUMBER < 0x01000000)
69
#define OPENSSL_NO_THREADID
70
#endif
71
72
#include <apr_portable.h>
73
74
/*
75
 * clean any suspicious headers in the HTTP request sent by the user agent
76
 */
77
1.22k
static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix, apr_hash_t *scrub) {
78
79
1.22k
  const int prefix_len = claim_prefix ? (int)_oidc_strlen(claim_prefix) : 0;
80
81
  /* get an array representation of the incoming HTTP headers */
82
1.22k
  const apr_array_header_t *const h = apr_table_elts(r->headers_in);
83
84
  /* table to keep the non-suspicious headers */
85
1.22k
  apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts);
86
87
  /* loop over the incoming HTTP headers */
88
1.22k
  const apr_table_entry_t *const e = (const apr_table_entry_t *)h->elts;
89
4.86k
  for (int i = 0; i < h->nelts; i++) {
90
3.64k
    const char *const k = e[i].key;
91
92
    /* is this header's name equivalent to a header that needs scrubbing? */
93
3.64k
    const char *hdr = (k != NULL) && (scrub != NULL) ? apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL;
94
3.64k
    const int header_matches = (hdr != NULL) && (oidc_util_strnenvcmp(k, hdr, -1) == 0);
95
96
    /*
97
     * would this header be interpreted as a mod_auth_openidc attribute? Note
98
     * that prefix_len will be zero if no attr_prefix is defined,
99
     * so this will always be false. Also note that we do not
100
     * scrub headers if the prefix is empty because every header
101
     * would match.
102
     */
103
3.64k
    const int prefix_matches =
104
3.64k
        (k != NULL) && prefix_len && (oidc_util_strnenvcmp(k, claim_prefix, prefix_len) == 0);
105
106
    /* add to the clean_headers if non-suspicious, skip and report otherwise */
107
3.64k
    if (!prefix_matches && !header_matches) {
108
2.42k
      apr_table_addn(clean_headers, k, e[i].val);
109
2.42k
    } else {
110
1.22k
      oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k, e[i].val);
111
1.22k
    }
112
3.64k
  }
113
114
  /* overwrite the incoming headers with the cleaned result */
115
1.22k
  r->headers_in = clean_headers;
116
1.22k
}
117
118
/*
119
 * scrub all mod_auth_openidc related headers
120
 */
121
1.22k
void oidc_scrub_headers(request_rec *r) {
122
1.22k
  const oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
123
124
1.22k
  const char *prefix = oidc_cfg_claim_prefix_get(cfg);
125
1.22k
  apr_hash_t *hdrs = apr_hash_make(r->pool);
126
127
1.22k
  if (_oidc_strcmp(prefix, "") == 0) {
128
0
    if ((oidc_cfg_white_listed_claims_get(cfg) != NULL) &&
129
0
        (apr_hash_count(oidc_cfg_white_listed_claims_get(cfg)) > 0))
130
0
      hdrs = apr_hash_overlay(r->pool, oidc_cfg_white_listed_claims_get(cfg), hdrs);
131
0
    else
132
0
      oidc_warn(r, "both " OIDCClaimPrefix " and " OIDCWhiteListedClaims
133
0
             " are empty: this renders an insecure setup!");
134
0
  }
135
136
1.22k
  const char *authn_hdr = oidc_cfg_dir_authn_header_get(r);
137
1.22k
  if (authn_hdr != NULL)
138
0
    apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr);
139
140
  /*
141
   * scrub all headers starting with OIDC_ first
142
   */
143
1.22k
  oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs);
144
145
  /*
146
   * then see if the claim headers need to be removed on top of that
147
   * (i.e. the prefix does not start with the default OIDC_)
148
   */
149
1.22k
  if (_oidc_strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix) {
150
0
    oidc_scrub_request_headers(r, prefix, NULL);
151
0
  }
152
1.22k
}
153
154
/*
155
 * return the configured cookie name that matches the leading "<name>=" portion of "cookie", or NULL when none matches
156
 */
157
0
static const char *oidc_strip_cookies_match(const char *cookie, const apr_array_header_t *strip) {
158
0
  for (int i = 0; i < strip->nelts; i++) {
159
0
    const char *name = APR_ARRAY_IDX(strip, i, const char *);
160
0
    size_t name_len = _oidc_strlen(name);
161
0
    if ((_oidc_strncmp(cookie, name, name_len) == 0) && (cookie[name_len] == OIDC_CHAR_EQUAL))
162
0
      return name;
163
0
  }
164
0
  return NULL;
165
0
}
166
167
/*
168
 * strip the session cookie from the headers sent to the application/backend
169
 */
170
907
void oidc_strip_cookies(request_rec *r) {
171
172
907
  const apr_array_header_t *strip = oidc_cfg_dir_strip_cookies_get(r);
173
907
  char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r));
174
175
907
  if ((cookies == NULL) || (strip == NULL))
176
907
    return;
177
178
0
  oidc_debug(r, "looking for the following cookies to strip from cookie header: %s",
179
0
       apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA));
180
181
0
  char *ctx = NULL;
182
0
  char *result = NULL;
183
0
  char *cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx);
184
0
  while (cookie != NULL) {
185
0
    const char *matched = NULL;
186
187
0
    while (*cookie == OIDC_CHAR_SPACE)
188
0
      cookie++;
189
190
    /* an all-whitespace token would otherwise survive as an empty cookie segment in the result */
191
0
    if (*cookie != '\0') {
192
0
      matched = oidc_strip_cookies_match(cookie, strip);
193
0
      if (matched != NULL) {
194
0
        oidc_debug(r, "stripping: %s", matched);
195
0
      } else {
196
0
        result = result ? apr_psprintf(r->pool, "%s%s %s", result, OIDC_STR_SEMI_COLON, cookie)
197
0
            : cookie;
198
0
      }
199
0
    }
200
201
0
    cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx);
202
0
  }
203
204
0
  oidc_http_hdr_in_cookie_set(r, result);
205
0
}
206
207
/*
208
 * check if s_json is valid provider metadata
209
 */
210
static apr_byte_t oidc_provider_validate_metadata_str(request_rec *r, oidc_cfg_t *c, const char *s_json,
211
0
                  oidc_json_t **j_provider, apr_byte_t decode_only) {
212
213
0
  if (oidc_json_decode_object(r, s_json, j_provider) == FALSE)
214
0
    return FALSE;
215
216
0
  if (decode_only == TRUE)
217
0
    return TRUE;
218
219
  /* check to see if it is valid metadata */
220
0
  if (oidc_metadata_provider_is_valid(r, c, *j_provider, NULL) == FALSE) {
221
0
    oidc_warn(r, "cache corruption detected: invalid metadata from url: %s",
222
0
        oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)));
223
0
    oidc_json_decref(*j_provider);
224
0
    *j_provider = NULL;
225
0
    return FALSE;
226
0
  }
227
228
0
  return TRUE;
229
0
}
230
231
/*
232
 * return the static provider configuration, i.e. from a metadata URL or configuration primitives
233
 */
234
8.50k
apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg_t *c, oidc_provider_t **provider) {
235
236
8.50k
  oidc_json_t *j_provider = NULL;
237
8.50k
  char *s_json = NULL;
238
239
  /* see if we should configure a static provider based on external (cached) metadata */
240
8.50k
  if ((oidc_cfg_metadata_dir_get(c) != NULL) ||
241
8.50k
      (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) == NULL)) {
242
8.50k
    *provider = oidc_cfg_provider_get(c);
243
8.50k
    return TRUE;
244
8.50k
  }
245
246
0
  oidc_cache_get_provider(r, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), &s_json);
247
248
0
  if (s_json != NULL)
249
0
    oidc_provider_validate_metadata_str(r, c, s_json, &j_provider, TRUE);
250
251
0
  if (j_provider == NULL) {
252
253
0
    if (oidc_metadata_provider_retrieve(r, c, NULL,
254
0
                oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)),
255
0
                &j_provider, &s_json) == FALSE) {
256
0
      oidc_error(r, "could not retrieve metadata from url: %s",
257
0
           oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)));
258
0
      return FALSE;
259
0
    }
260
0
    oidc_json_decref(j_provider);
261
262
0
    if (oidc_provider_validate_metadata_str(r, c, s_json, &j_provider, FALSE) == FALSE)
263
0
      return FALSE;
264
265
0
    oidc_cache_set_provider(
266
0
        r, oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)), s_json,
267
0
        apr_time_now() + apr_time_from_sec(oidc_cfg_provider_metadata_refresh_interval_get(c) <= 0
268
0
                 ? OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT
269
0
                 : oidc_cfg_provider_metadata_refresh_interval_get(c)));
270
0
  }
271
272
0
  *provider = oidc_cfg_provider_copy(r->pool, oidc_cfg_provider_get(c));
273
274
0
  if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) {
275
0
    oidc_error(r, "could not parse metadata from url: %s",
276
0
         oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)));
277
0
    oidc_json_decref(j_provider);
278
0
    return FALSE;
279
0
  }
280
281
0
  oidc_json_decref(j_provider);
282
283
0
  return TRUE;
284
0
}
285
286
/*
287
 * return the oidc_provider_t struct for the specified issuer
288
 */
289
oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg_t *c, const char *issuer,
290
4.45k
                apr_byte_t allow_discovery) {
291
292
  /* by default we'll assume that we're dealing with a single statically configured OP */
293
4.45k
  oidc_provider_t *provider = NULL;
294
4.45k
  if (oidc_provider_static_config(r, c, &provider) == FALSE)
295
0
    return NULL;
296
297
  /* if a metadata directory was configured, try and get the provider settings from there */
298
4.45k
  if ((oidc_cfg_metadata_dir_get(c) != NULL) &&
299
0
      ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery) == FALSE) || (provider == NULL))) {
300
    /* don't know nothing about this OP/issuer */
301
0
    oidc_error(r, "no provider metadata found for issuer \"%s\"", issuer);
302
0
    return NULL;
303
0
  }
304
305
4.45k
  return provider;
306
4.45k
}
307
308
/*
309
 * return the HTTP method being called: only for POST data persistence purposes
310
 */
311
2.49k
const char *oidc_original_request_method(request_rec *r, oidc_cfg_t *cfg, apr_byte_t handle_discovery_response) {
312
2.49k
  const char *method = OIDC_METHOD_GET;
313
314
2.49k
  char *m = NULL;
315
2.49k
  if ((handle_discovery_response == TRUE) && (oidc_util_url_matches_redirect_uri(r, cfg)) &&
316
174
      (oidc_is_discovery_response(r, cfg))) {
317
165
    oidc_util_url_parameter_get(r, OIDC_DISC_RM_PARAM, &m);
318
165
    if (m != NULL)
319
36
      method = apr_pstrdup(r->pool, m);
320
2.32k
  } else {
321
322
    /*
323
     * if POST preserve is not enabled for this location, there's no point in preserving
324
     * the method either which would result in POSTing empty data on return;
325
     * so we revert to legacy behavior
326
     */
327
2.32k
    if (oidc_cfg_dir_preserve_post_get(r) == 0)
328
1.61k
      return OIDC_METHOD_GET;
329
330
718
    const char *content_type = oidc_http_hdr_in_content_type_get(r);
331
718
    if ((r->method_number == M_POST) && (content_type != NULL) &&
332
718
        (_oidc_strcmp(content_type, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED) == 0))
333
718
      method = OIDC_METHOD_FORM_POST;
334
718
  }
335
336
883
  oidc_debug(r, "return: %s", method);
337
338
883
  return method;
339
2.49k
}
340
341
/*
342
 * set the claims from a JSON object (c.q. id_token or user_info response) stored
343
 * in the session in to HTTP headers passed on to the application
344
 */
345
0
apr_byte_t oidc_set_app_claims(request_rec *r, const oidc_cfg_t *cfg, oidc_json_t *claims) {
346
347
0
  oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r);
348
349
  // optimize performance when `OIDCPassClaimsAs none` is set
350
0
  if (pass_in == OIDC_APPINFO_PASS_NONE)
351
0
    return TRUE;
352
353
  /* set the resolved claims a HTTP headers for the application */
354
0
  if (claims != NULL)
355
0
    oidc_util_appinfo_set_all(r, claims, oidc_cfg_claim_prefix_get(cfg), oidc_cfg_claim_delimiter_get(cfg),
356
0
            pass_in, oidc_cfg_dir_pass_info_encoding_get(r));
357
358
0
  return TRUE;
359
0
}
360
361
/*
362
 * log message about max session duration
363
 */
364
416
void oidc_log_session_expires(request_rec *r, const char *msg, apr_time_t session_expires) {
365
416
  char buf[APR_RFC822_DATE_LEN + 1];
366
416
  apr_rfc822_date(buf, session_expires);
367
416
  oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf,
368
416
       apr_time_sec(session_expires - apr_time_now()));
369
416
}
370
371
/*
372
 * see if this is a request that is capable of completing an authentication round trip to the Provider
373
 */
374
0
apr_byte_t oidc_is_auth_capable_request(const request_rec *r) {
375
376
0
  if ((oidc_http_hdr_in_x_requested_with_get(r) != NULL) &&
377
0
      (_oidc_strnatcasecmp(oidc_http_hdr_in_x_requested_with_get(r), OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
378
0
    return FALSE;
379
380
0
  if ((oidc_http_hdr_in_sec_fetch_mode_get(r) != NULL) &&
381
0
      (_oidc_strnatcasecmp(oidc_http_hdr_in_sec_fetch_mode_get(r), OIDC_HTTP_HDR_VAL_NAVIGATE) != 0))
382
0
    return FALSE;
383
384
0
  if ((oidc_http_hdr_in_sec_fetch_dest_get(r) != NULL) &&
385
0
      (_oidc_strnatcasecmp(oidc_http_hdr_in_sec_fetch_dest_get(r), OIDC_HTTP_HDR_VAL_DOCUMENT) != 0))
386
0
    return FALSE;
387
388
0
  if ((oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_TEXT_HTML) == FALSE) &&
389
0
      (oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_APP_XHTML_XML) == FALSE) &&
390
0
      (oidc_http_hdr_in_accept_contains(r, OIDC_HTTP_CONTENT_TYPE_ANY) == FALSE))
391
0
    return FALSE;
392
393
0
  return TRUE;
394
0
}
395
396
/*
397
 * find out which action we need to take when encountering an unauthenticated request
398
 */
399
0
static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg_t *c) {
400
401
  /* see if we've configured OIDCUnAuthAction for this path */
402
0
  switch (oidc_cfg_dir_unauth_action_get(r)) {
403
0
  case OIDC_UNAUTH_RETURN410:
404
0
    return HTTP_GONE;
405
0
  case OIDC_UNAUTH_RETURN407:
406
0
    return HTTP_PROXY_AUTHENTICATION_REQUIRED;
407
0
  case OIDC_UNAUTH_RETURN401:
408
0
    return HTTP_UNAUTHORIZED;
409
0
  case OIDC_UNAUTH_PASS:
410
0
    r->user = "";
411
412
    /*
413
     * we're not going to pass information about an authenticated user to the application,
414
     * but we do need to scrub the headers that mod_auth_openidc would set for security reasons
415
     */
416
0
    oidc_scrub_headers(r);
417
418
0
    return OK;
419
420
0
  case OIDC_UNAUTH_AUTHENTICATE:
421
422
    /*
423
     * exception handling: if this looks like a XMLHttpRequest call we
424
     * won't redirect the user and thus avoid creating a state cookie
425
     * for a non-browser (= Javascript) call that will never return from the OP
426
     */
427
0
    if ((oidc_cfg_dir_unauth_expr_is_set(r) == FALSE) && (oidc_is_auth_capable_request(r) == FALSE))
428
0
      return HTTP_UNAUTHORIZED;
429
0
  }
430
431
  /*
432
   * else: no session (regardless of whether it is main or sub-request),
433
   * and we need to authenticate the user
434
   */
435
0
  return oidc_request_authenticate_user(r, c, NULL, oidc_util_url_cur(r, oidc_cfg_x_forwarded_headers_get(c)),
436
0
                NULL, NULL, NULL, oidc_cfg_dir_path_auth_request_params_get(r),
437
0
                oidc_cfg_dir_path_scope_get(r));
438
0
}
439
440
/*
441
 * check if maximum session duration was exceeded
442
 */
443
0
static apr_byte_t oidc_check_max_session_duration(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, int *rc) {
444
445
  /* get the session expiry from the session data */
446
0
  apr_time_t session_expires = oidc_session_get_session_expires(r, session);
447
448
  /* check the expire timestamp against the current time */
449
0
  if (apr_time_now() > session_expires) {
450
0
    oidc_warn(r, "maximum session duration exceeded for user: %s", session->remote_user);
451
0
    oidc_session_kill(r, session);
452
0
    *rc = oidc_handle_unauthenticated_user(r, cfg);
453
0
    return FALSE;
454
0
  }
455
456
  /* log message about max session duration */
457
0
  oidc_log_session_expires(r, "session max lifetime", session_expires);
458
459
0
  *rc = OK;
460
461
0
  return TRUE;
462
0
}
463
464
/* Reject session cookies issued for another host sharing the cache or encryption key. */
465
49
apr_byte_t oidc_check_cookie_domain(request_rec *r, const oidc_cfg_t *cfg, const oidc_session_t *session) {
466
49
  const char *c_cookie_domain = oidc_cfg_cookie_domain_get(cfg)
467
49
            ? oidc_cfg_cookie_domain_get(cfg)
468
49
            : oidc_util_url_cur_host(r, oidc_cfg_x_forwarded_headers_get(cfg));
469
49
  const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session);
470
49
  if ((s_cookie_domain == NULL) || (_oidc_strnatcasecmp(c_cookie_domain, s_cookie_domain) != 0)) {
471
49
    oidc_warn(r,
472
49
        "aborting: detected attempt to play cookie against a different domain/host than issued for! "
473
49
        "(issued=%s, current=%s)",
474
49
        s_cookie_domain, c_cookie_domain);
475
49
    return FALSE;
476
49
  }
477
478
0
  return TRUE;
479
49
}
480
481
/*
482
 * get a handle to the provider configuration via the "issuer" stored in the session
483
 */
484
apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg_t *c, const oidc_session_t *session,
485
353
            oidc_provider_t **provider) {
486
487
353
  oidc_debug(r, "enter");
488
489
  /* get the issuer value from the session state */
490
353
  const char *issuer = oidc_session_get_issuer(r, session);
491
353
  if (issuer == NULL) {
492
141
    oidc_warn(r, "empty or invalid session: no issuer found");
493
141
    return FALSE;
494
141
  }
495
496
  /* get the provider info associated with the issuer value */
497
212
  oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
498
212
  if (p == NULL) {
499
0
    oidc_error(r, "session corrupted: no provider found for issuer: %s", issuer);
500
0
    return FALSE;
501
0
  }
502
503
212
  *provider = p;
504
505
212
  return TRUE;
506
212
}
507
508
/*
509
 * copy the claims and id_token from the session to the request state
510
 */
511
0
static void oidc_copy_tokens_to_request_state(request_rec *r, const oidc_session_t *session) {
512
513
0
  const oidc_json_t *id_token = oidc_session_get_idtoken_claims(r, session);
514
0
  const oidc_json_t *claims = oidc_session_get_userinfo_claims(r, session);
515
0
  const char *scope = oidc_session_get_scope(r, session);
516
517
0
  if (id_token != NULL)
518
0
    oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token);
519
520
0
  if (claims != NULL)
521
0
    oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims);
522
523
0
  if (scope != NULL)
524
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SCOPE, scope);
525
0
}
526
527
/*
528
 * pass refresh_token, access_token and access_token_expires as headers/environment variables to the application
529
 */
530
void oidc_session_pass_tokens(request_rec *r, const oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t extend_session,
531
0
            apr_byte_t *needs_save) {
532
533
0
  oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r);
534
0
  oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r);
535
536
  /* set the refresh_token in the app headers/variables, if enabled for this location/directory */
537
0
  const char *refresh_token = oidc_session_get_refresh_token(r, session);
538
0
  if ((oidc_cfg_dir_pass_refresh_token_get(r) != 0) && (refresh_token != NULL)) {
539
    /* pass it to the app in a header or environment variable */
540
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_REFRESH_TOKEN, refresh_token, OIDC_DEFAULT_HEADER_PREFIX,
541
0
              pass_in, encoding);
542
0
  }
543
544
  /* set the access_token in the app headers/variables */
545
0
  const char *access_token = oidc_session_get_access_token(r, session);
546
0
  if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token != NULL) {
547
    /* pass it to the app in a header or environment variable */
548
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token, OIDC_DEFAULT_HEADER_PREFIX, pass_in,
549
0
              encoding);
550
0
  }
551
552
  /* set the access_token type in the app headers/variables */
553
0
  const char *access_token_type = oidc_session_get_access_token_type(r, session);
554
0
  if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_type != NULL) {
555
    /* pass it to the app in a header or environment variable */
556
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_ACCESS_TOKEN_TYPE, access_token_type, OIDC_DEFAULT_HEADER_PREFIX,
557
0
              pass_in, encoding);
558
0
  }
559
560
  /* set the expiry timestamp in the app headers/variables */
561
0
  const char *access_token_expires = oidc_session_get_access_token_expires2str(r, session);
562
0
  if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_expires != NULL) {
563
    /* pass it to the app in a header or environment variable */
564
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_ACCESS_TOKEN_EXP, access_token_expires,
565
0
              OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
566
0
  }
567
568
  /* set the scope in the app headers/variables alongside of the access token, if enabled */
569
0
  const char *scope = oidc_session_get_scope(r, session);
570
0
  if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && scope != NULL) {
571
    /* pass it to the app in a header or environment variable */
572
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_SCOPE, scope, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
573
0
  }
574
575
0
  if (extend_session) {
576
    /*
577
     * Limit inactivity updates to once per 10% of the timeout, capped at 60 seconds. This
578
     * reduces writes but may expire a session by up to that interval earlier than expected.
579
     */
580
0
    apr_time_t interval = apr_time_from_sec(oidc_cfg_session_inactivity_timeout_get(cfg));
581
0
    apr_time_t now = apr_time_now();
582
0
    apr_time_t slack = interval / 10;
583
0
    if (slack > apr_time_from_sec(60))
584
0
      slack = apr_time_from_sec(60);
585
0
    if (session->expiry - now < interval - slack) {
586
0
      session->expiry = now + interval;
587
0
      *needs_save = TRUE;
588
0
    }
589
0
  }
590
591
  // if this is a newly created session, we'll write it again to update the samesite setting on the session cookie
592
0
  if (oidc_session_get_session_new(r, session)) {
593
0
    *needs_save = TRUE;
594
0
    oidc_session_set_session_new(r, session, 0);
595
0
  }
596
597
  /* log message about session expiry */
598
0
  oidc_log_session_expires(r, "session inactivity timeout", session->expiry);
599
0
}
600
601
static void oidc_idtoken_pass_as(request_rec *r, const oidc_cfg_t *cfg, const oidc_session_t *session,
602
0
         oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) {
603
604
0
  if (oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_OFF)
605
0
    return;
606
607
0
  if (oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_CLAIMS) {
608
    /* set the id_token in the app headers */
609
0
    oidc_set_app_claims(r, cfg, oidc_session_get_idtoken_claims(r, session));
610
0
  }
611
612
0
  if (oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_PAYLOAD) {
613
    /* pass the id_token JSON object to the app in a header or environment variable */
614
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_ID_TOKEN_PAYLOAD,
615
0
              oidc_json_encode(r->pool, oidc_session_get_idtoken_claims(r, session),
616
0
                   OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT),
617
0
              OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
618
0
  }
619
620
0
  if (oidc_cfg_dir_pass_idtoken_as_get(r) & OIDC_PASS_IDTOKEN_AS_SERIALIZED) {
621
    /* pass the compact serialized JWT to the app in a header or environment variable */
622
0
    oidc_util_appinfo_set(r, OIDC_APP_INFO_ID_TOKEN, oidc_session_get_idtoken(r, session),
623
0
              OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
624
0
  }
625
0
}
626
627
/*
628
 * handle the case where we have identified an existing authentication session for a user
629
 */
630
/*
631
 * apply the configured action after a failed access-token or userinfo refresh in an existing
632
 * session: single logout, forced re-authentication, or a 502 towards the application
633
 */
634
static int oidc_handle_session_refresh_error(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session,
635
0
               oidc_on_error_action_t action) {
636
0
  if (action == OIDC_ON_ERROR_LOGOUT)
637
0
    return oidc_logout_request(r, cfg, session,
638
0
             oidc_util_url_abs(r, cfg, oidc_cfg_default_slo_url_get(cfg)), FALSE);
639
0
  if (action == OIDC_ON_ERROR_AUTH) {
640
0
    oidc_session_kill(r, session);
641
0
    return oidc_handle_unauthenticated_user(r, cfg);
642
0
  }
643
0
  return HTTP_BAD_GATEWAY;
644
0
}
645
646
static int oidc_handle_existing_session(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session,
647
49
          apr_byte_t extend_session, apr_byte_t *needs_save) {
648
649
49
  apr_byte_t rv = FALSE;
650
49
  int rc = OK;
651
652
49
  oidc_debug(r, "enter");
653
654
  /* set the user in the main request for further (incl. sub-request) processing */
655
49
  r->user = apr_pstrdup(r->pool, session->remote_user);
656
49
  oidc_debug(r, "set remote_user to \"%s\" in existing session \"%s\"", r->user,
657
49
       oidc_session_id_fingerprint(r, session->uuid));
658
659
  /* get the header name in which the remote user name needs to be passed */
660
49
  const char *authn_header = oidc_cfg_dir_authn_header_get(r);
661
662
49
  oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r);
663
49
  oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r);
664
665
  /* verify current cookie domain against issued cookie domain */
666
49
  if (oidc_check_cookie_domain(r, cfg, session) == FALSE) {
667
49
    *needs_save = FALSE;
668
49
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_COOKIE_DOMAIN);
669
49
    return HTTP_UNAUTHORIZED;
670
49
  }
671
672
  /*
673
   * we're going to pass the information that we have to the application,
674
   * but first we need to scrub the headers that we're going to use for security reasons
675
   * NB: need it before oidc_check_max_session_duration since OIDCUnAuthAction pass may be set
676
   */
677
0
  oidc_scrub_headers(r);
678
679
  /* check if the maximum session duration was exceeded */
680
0
  if (oidc_check_max_session_duration(r, cfg, session, &rc) == FALSE) {
681
0
    *needs_save = FALSE;
682
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_EXPIRED);
683
    // NB: rc was set (e.g. to a 302 auth redirect) by the call to oidc_check_max_session_duration
684
0
    return rc;
685
0
  }
686
687
0
  if (extend_session) {
688
689
    /* if needed, refresh the access token */
690
0
    rv = oidc_refresh_access_token_before_expiry(
691
0
        r, cfg, session, oidc_cfg_dir_refresh_access_token_before_expiry_get(r), needs_save);
692
0
    if (rv == FALSE) {
693
0
      *needs_save = FALSE;
694
0
      oidc_debug(r, "dir_action_on_error_refresh: %d", oidc_cfg_dir_action_on_error_refresh_get(r));
695
0
      OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_ACCESS_TOKEN);
696
0
      return oidc_handle_session_refresh_error(r, cfg, session,
697
0
                 oidc_cfg_dir_action_on_error_refresh_get(r));
698
0
    }
699
700
    /* if needed, refresh claims from the user info endpoint */
701
0
    rv = oidc_userinfo_refresh_claims(r, cfg, session, needs_save);
702
0
    if (rv == FALSE) {
703
0
      *needs_save = FALSE;
704
0
      oidc_debug(r, "action_on_userinfo_error: %d", oidc_cfg_action_on_userinfo_error_get(cfg));
705
0
      OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_USERINFO);
706
0
      return oidc_handle_session_refresh_error(r, cfg, session,
707
0
                 oidc_cfg_action_on_userinfo_error_get(cfg));
708
0
    }
709
0
  }
710
711
  /* set the user authentication HTTP header if set and required */
712
0
  if ((r->user != NULL) && (authn_header != NULL))
713
0
    oidc_http_hdr_in_set(r, authn_header, r->user);
714
715
  /* copy id_token and claims from session to request state and obtain their values */
716
0
  oidc_copy_tokens_to_request_state(r, session);
717
718
  /* pass the at, rt and at expiry to the application, possibly update the session expiry */
719
0
  oidc_session_pass_tokens(r, cfg, session, extend_session, needs_save);
720
721
  /* pass ID token and claims */
722
0
  oidc_idtoken_pass_as(r, cfg, session, pass_in, encoding);
723
  /* pass userinfo claims */
724
0
  oidc_userinfo_pass_as(r, cfg, session, pass_in, encoding);
725
726
  /* return "user authenticated" status */
727
0
  return OK;
728
0
}
729
730
/*
731
 * get the r->user for this request based on the configuration for OIDC/OAuth
732
 */
733
apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *reg_exp, const char *replace,
734
1.34k
        const oidc_json_t *json, char **request_user) {
735
736
  /* get the claim value from the JSON object */
737
1.34k
  const oidc_json_t *username = oidc_json_object_get(json, claim_name);
738
1.34k
  if ((username == NULL) || (!oidc_json_is_string(username))) {
739
21
    oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name);
740
21
    return FALSE;
741
21
  }
742
743
1.32k
  *request_user = apr_pstrdup(r->pool, oidc_json_string_value(username));
744
745
1.32k
  if (reg_exp != NULL) {
746
747
0
    char *error_str = NULL;
748
749
0
    if (replace == NULL) {
750
751
0
      if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp, request_user, &error_str) ==
752
0
          FALSE) {
753
0
        oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str);
754
0
        *request_user = NULL;
755
0
        return FALSE;
756
0
      }
757
758
0
    } else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp, replace, request_user,
759
0
                   &error_str) == FALSE) {
760
761
0
      oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str);
762
0
      *request_user = NULL;
763
0
      return FALSE;
764
0
    }
765
0
  }
766
767
1.32k
  return TRUE;
768
1.32k
}
769
770
3.43k
#define OIDC_MAX_URL_LENGTH (8192 * 2)
771
772
/*
773
 * fill the err_str/err_desc out-params, log the error and return FALSE
774
 */
775
static apr_byte_t oidc_validate_redirect_url_fail(request_rec *r, char **err_str, char **err_desc, const char *str,
776
974
              const char *desc) {
777
974
  *err_str = apr_pstrdup(r->pool, str);
778
974
  *err_desc = apr_pstrdup(r->pool, desc);
779
974
  oidc_error(r, "%s: %s", *err_str, *err_desc);
780
974
  return FALSE;
781
974
}
782
783
/*
784
 * verify the URL matches one of the OIDCRedirectURLsAllowed regexes
785
 */
786
static apr_byte_t oidc_validate_redirect_url_allowed(request_rec *r, apr_hash_t *allowed, const char *url,
787
177
                 char **err_str, char **err_desc) {
788
177
  const char *c_host = NULL;
789
518
  for (apr_hash_index_t *hi = apr_hash_first(NULL, allowed); hi; hi = apr_hash_next(hi)) {
790
353
    apr_hash_this(hi, (const void **)&c_host, NULL, NULL);
791
353
    if (oidc_util_regexp_first_match(r->pool, url, c_host, NULL, err_str) == TRUE)
792
12
      return TRUE;
793
353
  }
794
165
  return oidc_validate_redirect_url_fail(
795
165
      r, err_str, err_desc, "URL not allowed",
796
165
      apr_psprintf(r->pool, "value does not match the list of allowed redirect URLs: %s", url));
797
177
}
798
799
/*
800
 * verify the URL hostname matches the hostname of the current request
801
 */
802
static apr_byte_t oidc_validate_redirect_url_host(request_rec *r, const oidc_cfg_t *c, apr_uri_t *uri, char **err_str,
803
2.37k
              char **err_desc) {
804
2.37k
  const char *c_host = oidc_util_url_cur_host(r, oidc_cfg_x_forwarded_headers_get(c));
805
  /* IPv6 literals need to be wrapped in brackets to compare with the current hostname */
806
2.37k
  const char *url_ipv6_aware =
807
2.37k
      strchr(uri->hostname, ':') ? apr_pstrcat(r->pool, "[", uri->hostname, "]", NULL) : uri->hostname;
808
2.37k
  if (_oidc_strnatcasecmp(c_host, url_ipv6_aware) == 0)
809
2.18k
    return TRUE;
810
198
  return oidc_validate_redirect_url_fail(
811
198
      r, err_str, err_desc, "Invalid Request",
812
198
      apr_psprintf(r->pool, "URL value \"%s\" does not match the hostname of the current request \"%s\"",
813
198
       apr_uri_unparse(r->pool, uri, 0), c_host));
814
2.37k
}
815
816
/*
817
 * for hostname-less URLs, require the URL to be a safe relative path
818
 */
819
static apr_byte_t oidc_validate_redirect_url_relative(request_rec *r, const char *url, char **err_str,
820
668
                  char **err_desc) {
821
668
  if (_oidc_strstr(url, "/") != url)
822
399
    return oidc_validate_redirect_url_fail(
823
399
        r, err_str, err_desc, "Malformed URL",
824
399
        apr_psprintf(
825
399
      r->pool,
826
399
      "No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s", url));
827
269
  if (_oidc_strstr(url, "//") == url)
828
33
    return oidc_validate_redirect_url_fail(
829
33
        r, err_str, err_desc, "Malformed URL",
830
33
        apr_psprintf(r->pool, "No hostname was parsed and starting with '//': %s", url));
831
236
  if (_oidc_strstr(url, "/\\") == url)
832
0
    return oidc_validate_redirect_url_fail(
833
0
        r, err_str, err_desc, "Malformed URL",
834
0
        apr_psprintf(r->pool, "No hostname was parsed and starting with '/\\': %s", url));
835
236
  return TRUE;
836
236
}
837
838
/*
839
 * reject the URL when it contains characters used for HTTP header splitting or other smuggling tricks
840
 */
841
/*
842
 * substrings that must not occur in a URL that is redirected to: URL-smuggling and scheme-injection
843
 * vectors such as (percent-encoded) tab/slash/backslash separators, embedded scheme prefixes, and
844
 * CJK look-alike separator characters; each entry states whether it is matched case-insensitively
845
 */
846
static const struct {
847
  const char *needle;
848
  apr_byte_t case_insensitive;
849
} _oidc_redirect_url_illegal_substrings[] = {
850
    {"/%09", FALSE},  {"/%2f", TRUE},  {"/\t", FALSE},   {"/%68", FALSE},
851
    {"/http:", TRUE}, {"/https:", TRUE}, {"/javascript:", TRUE}, {"%01javascript:", TRUE},
852
    {"/〱", FALSE},   {"/〵", FALSE},  {"/ゝ", FALSE},  {"/ー", FALSE},
853
    {"/ー", FALSE},    {"/<", FALSE},   {"/%5c", FALSE},  {"/\\", FALSE},
854
    {NULL, FALSE},
855
};
856
857
2.49k
static apr_byte_t oidc_validate_redirect_url_chars(request_rec *r, const char *url, char **err_str, char **err_desc) {
858
2.49k
  if ((_oidc_strstr(url, "\n") != NULL) || (_oidc_strstr(url, "\r") != NULL))
859
27
    return oidc_validate_redirect_url_fail(
860
27
        r, err_str, err_desc, "Invalid URL",
861
27
        apr_psprintf(r->pool, "URL value \"%s\" contains illegal \"\n\" or \"\r\" character(s)", url));
862
863
41.2k
  for (int i = 0; _oidc_redirect_url_illegal_substrings[i].needle != NULL; i++) {
864
38.8k
    const char *needle = _oidc_redirect_url_illegal_substrings[i].needle;
865
38.8k
    const char *found = _oidc_redirect_url_illegal_substrings[i].case_insensitive
866
38.8k
          ? oidc_util_strcasestr(url, needle)
867
38.8k
          : _oidc_strstr(url, needle);
868
38.8k
    if (found != NULL)
869
67
      return oidc_validate_redirect_url_fail(
870
67
          r, err_str, err_desc, "Invalid URL",
871
67
          apr_psprintf(r->pool, "URL value \"%s\" contains illegal character(s)", url));
872
38.8k
  }
873
874
2.40k
  return TRUE;
875
2.47k
}
876
877
/*
878
 * avoid cross site request forgery on the redirect_to_url
879
 */
880
apr_byte_t oidc_validate_redirect_url(request_rec *r, const oidc_cfg_t *c, const char *redirect_to_url,
881
3.37k
              oidc_redirect_url_scope_t scope, char **err_str, char **err_desc) {
882
3.37k
  apr_uri_t uri;
883
3.37k
  if (redirect_to_url == NULL)
884
0
    return oidc_validate_redirect_url_fail(r, err_str, err_desc, "Invalid URL", "URL value is NULL");
885
3.37k
  if (_oidc_strlen(redirect_to_url) > OIDC_MAX_URL_LENGTH)
886
56
    return oidc_validate_redirect_url_fail(
887
56
        r, err_str, err_desc, "URL too long",
888
56
        apr_psprintf(r->pool, "URL value exceeds the maximum length of %d bytes", OIDC_MAX_URL_LENGTH));
889
3.32k
  char *url = apr_pstrdup(r->pool, redirect_to_url);
890
891
  // replace potentially harmful backslashes with forward slashes
892
1.77M
  for (size_t i = 0; i < _oidc_strlen(url); i++)
893
1.77M
    if (url[i] == '\\')
894
34.2k
      url[i] = '/';
895
896
3.32k
  if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS)
897
29
    return oidc_validate_redirect_url_fail(r, err_str, err_desc, "Malformed URL",
898
29
                   apr_psprintf(r->pool, "not a valid URL value: %s", url));
899
900
3.29k
  if (oidc_cfg_redirect_urls_allowed_get(c) != NULL) {
901
177
    if (oidc_validate_redirect_url_allowed(r, oidc_cfg_redirect_urls_allowed_get(c), url, err_str,
902
177
                   err_desc) == FALSE)
903
165
      return FALSE;
904
3.11k
  } else if ((uri.hostname != NULL) && (scope == OIDC_REDIRECT_URL_SAME_HOST) &&
905
2.37k
       (oidc_validate_redirect_url_host(r, c, &uri, err_str, err_desc) == FALSE)) {
906
198
    return FALSE;
907
198
  }
908
909
2.93k
  if ((uri.hostname == NULL) && (oidc_validate_redirect_url_relative(r, url, err_str, err_desc) == FALSE))
910
432
    return FALSE;
911
912
  /* validate the URL to prevent HTTP header splitting */
913
2.49k
  return oidc_validate_redirect_url_chars(r, url, err_str, err_desc);
914
2.93k
}
915
916
/*
917
 * return the Javascript code used to handle an Implicit grant type
918
 * i.e. that posts the data returned by the OP in the URL fragment to the OIDCRedirectURI
919
 */
920
3
static int oidc_javascript_implicit(request_rec *r, oidc_cfg_t *c) {
921
922
3
  oidc_debug(r, "enter");
923
924
3
  const char *java_script =
925
3
      "    <script type=\"text/javascript\">\n"
926
3
      "      function postOnLoad() {\n"
927
3
      "        encoded = location.hash.substring(1).split('&');\n"
928
3
      "        for (i = 0; i < encoded.length; i++) {\n"
929
3
      "          encoded[i] = encoded[i].replace(/\\+/g, ' ');\n"
930
3
      "          var n = encoded[i].indexOf('=');\n"
931
3
      "          var input = document.createElement('input');\n"
932
3
      "          input.type = 'hidden';\n"
933
3
      "          input.name = decodeURIComponent(encoded[i].substring(0, n));\n"
934
3
      "          input.value = decodeURIComponent(encoded[i].substring(n+1));\n"
935
3
      "          document.forms[0].appendChild(input);\n"
936
3
      "        }\n"
937
3
      "        document.forms[0].action = window.location.href.substr(0, window.location.href.indexOf('#'));\n"
938
3
      "        HTMLFormElement.prototype.submit.call(document.forms[0]);\n"
939
3
      "      }\n"
940
3
      "    </script>\n";
941
942
3
  const char *html_body = "    <p>Submitting...</p>\n"
943
3
        "    <form method=\"post\" action=\"\">\n"
944
3
        "      <p>\n"
945
3
        "        <input type=\"hidden\" name=\"" OIDC_PROTO_RESPONSE_MODE
946
3
        "\" value=\"" OIDC_PROTO_RESPONSE_MODE_FRAGMENT "\">\n"
947
3
        "      </p>\n"
948
3
        "    </form>\n";
949
950
  /* prepare HTML/Javascript page to be sent in the content handler */
951
3
  return oidc_util_html_content_prep(r, OIDC_REQUEST_STATE_KEY_HTML, "Submitting...", java_script, "postOnLoad()",
952
3
             html_body);
953
3
}
954
955
/*
956
 * handle an authorization response from the OP using the Basic Client profile or a Hybrid flow
957
 */
958
6
static int oidc_redirect_uri_handle_response_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
959
6
  return oidc_response_authorization_redirect(r, c, session);
960
6
}
961
962
/*
963
 * handle an authorization response using the fragment(+POST) response_mode with the Implicit Client profile
964
 */
965
1.76k
static apr_byte_t oidc_redirect_uri_match_response_post(request_rec *r, oidc_cfg_t *c) {
966
1.76k
  return oidc_proto_response_is_post(r, c);
967
1.76k
}
968
969
/*
970
 * handle a response from the OP discovery page
971
 */
972
591
static int oidc_redirect_uri_handle_discovery_response(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
973
591
  return oidc_discovery_response(r, c);
974
591
}
975
976
/*
977
 * pass the request on to the content handler; avoid:
978
 * "No authentication done but request not allowed without authentication"
979
 * by setting r->user
980
 */
981
115
static int oidc_redirect_uri_handle_in_content_handler(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
982
  /* no authentication happened, so any OIDC_* headers on this request are the
983
   * client's own and must not be passed on */
984
115
  oidc_scrub_headers(r);
985
115
  r->user = "";
986
115
  return OK;
987
115
}
988
989
/*
990
 * handle a request object by reference request
991
 */
992
9
static int oidc_redirect_uri_handle_request_uri(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
993
9
  return oidc_request_uri(r, c);
994
9
}
995
996
/*
997
 * handle a request to invalidate the access token cache
998
 */
999
2
static int oidc_redirect_uri_handle_remove_at_cache(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1000
2
  return oidc_revoke_at_cache_remove(r, c);
1001
2
}
1002
1003
/*
1004
 * handle a request to revoke a user session
1005
 */
1006
6
static int oidc_redirect_uri_handle_revoke_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1007
6
  return oidc_revoke_session(r, c);
1008
6
}
1009
1010
/*
1011
 * handle a request to the info hook
1012
 */
1013
49
static int oidc_redirect_uri_handle_info(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1014
49
  apr_byte_t needs_save = FALSE;
1015
49
  char *s_extend_session = NULL;
1016
49
  int rc = OK;
1017
1018
49
  oidc_util_url_parameter_get(r, OIDC_INFO_PARAM_EXTEND_SESSION, &s_extend_session);
1019
1020
  // need to establish user/claims for authorization purposes
1021
49
  rc = oidc_handle_existing_session(
1022
49
      r, c, session, (s_extend_session == NULL) || (_oidc_strcmp(s_extend_session, "false") != 0), &needs_save);
1023
1024
  // retain this session across the authentication and content handler phases
1025
  // by storing it in the request state
1026
49
  apr_pool_userdata_set(session, OIDC_USERDATA_SESSION, NULL, r->pool);
1027
1028
  // record whether the session was modified and needs to be saved in the cache
1029
49
  if (needs_save)
1030
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SAVE, "");
1031
1032
49
  return rc;
1033
49
}
1034
1035
/*
1036
 * match a "bare" request to the redirect URI, indicating implicit flow using the fragment response_mode
1037
 */
1038
7
static apr_byte_t oidc_redirect_uri_match_bare(request_rec *r, oidc_cfg_t *c) {
1039
7
  return (r->args == NULL) || (_oidc_strcmp(r->args, "") == 0);
1040
7
}
1041
1042
3
static int oidc_redirect_uri_handle_implicit(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1043
3
  return oidc_javascript_implicit(r, c);
1044
3
}
1045
1046
/* dispatch-table entry for one redirect_uri sub-feature */
1047
typedef struct oidc_redirect_uri_dispatch_t {
1048
  /* matches the request to this sub-feature; when NULL the request matches on the query parameter below */
1049
  apr_byte_t (*match)(request_rec *r, oidc_cfg_t *c);
1050
  /* query parameter that selects this sub-feature when match is NULL */
1051
  const char *parameter;
1052
  /* require an authenticated session, returning HTTP_UNAUTHORIZED (before counting) when there is none */
1053
  apr_byte_t requires_auth;
1054
  /* handles the matched request */
1055
  int (*handle)(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);
1056
  /* metrics counter identifying this sub-feature */
1057
  oidc_metrics_counter_type_t metric;
1058
  /* add the authentication-response timing metric after handling */
1059
  apr_byte_t timing;
1060
} oidc_redirect_uri_dispatch_t;
1061
1062
/*
1063
 * Ordered redirect_uri dispatch. Logout precedes POST authorization responses so back-channel
1064
 * logout reaches its handler.
1065
 */
1066
// clang-format off
1067
static const oidc_redirect_uri_dispatch_t _oidc_redirect_uri_dispatch[] = {
1068
    {oidc_proto_response_is_redirect, NULL, FALSE, oidc_redirect_uri_handle_response_redirect, OM_REDIRECT_URI_AUTHN_RESPONSE_REDIRECT, TRUE},
1069
    {NULL, OIDC_REDIRECT_URI_REQUEST_LOGOUT, FALSE, oidc_logout, OM_REDIRECT_URI_REQUEST_LOGOUT, FALSE},
1070
    {oidc_redirect_uri_match_response_post, NULL, FALSE, oidc_response_authorization_post, OM_REDIRECT_URI_AUTHN_RESPONSE_POST, TRUE},
1071
    {oidc_is_discovery_response, NULL, FALSE, oidc_redirect_uri_handle_discovery_response, OM_REDIRECT_URI_DISCOVERY_RESPONSE, FALSE},
1072
    {NULL, OIDC_REDIRECT_URI_REQUEST_JWKS, FALSE, oidc_redirect_uri_handle_in_content_handler, OM_REDIRECT_URI_REQUEST_JWKS, FALSE},
1073
    {NULL, OIDC_REDIRECT_URI_REQUEST_SESSION, FALSE, oidc_session_management, OM_REDIRECT_URI_REQUEST_SESSION, FALSE},
1074
    {NULL, OIDC_REDIRECT_URI_REQUEST_REFRESH, FALSE, oidc_refresh_token_request, OM_REDIRECT_URI_REQUEST_REFRESH, FALSE},
1075
    {NULL, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, FALSE, oidc_redirect_uri_handle_request_uri, OM_REDIRECT_URI_REQUEST_REQUEST_URI, FALSE},
1076
    {NULL, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, FALSE, oidc_redirect_uri_handle_remove_at_cache, OM_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, FALSE},
1077
    {NULL, OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION, FALSE, oidc_redirect_uri_handle_revoke_session, OM_REDIRECT_URI_REQUEST_REVOKE_SESSION, FALSE},
1078
    {NULL, OIDC_REDIRECT_URI_REQUEST_DPOP, FALSE, oidc_redirect_uri_handle_in_content_handler, OM_REDIRECT_URI_REQUEST_DPOP, FALSE},
1079
    {NULL, OIDC_REDIRECT_URI_REQUEST_INFO, TRUE, oidc_redirect_uri_handle_info, OM_REDIRECT_URI_REQUEST_INFO, FALSE},
1080
    {oidc_redirect_uri_match_bare, NULL, FALSE, oidc_redirect_uri_handle_implicit, OM_REDIRECT_URI_AUTHN_RESPONSE_IMPLICIT, FALSE},
1081
};
1082
// clang-format on
1083
1084
/*
1085
 * handle all requests to the redirect_uri
1086
 */
1087
2.43k
int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1088
1089
2.43k
  const oidc_redirect_uri_dispatch_t *entry = NULL;
1090
2.43k
  int rc = OK;
1091
1092
2.43k
  OIDC_METRICS_TIMING_START(r, c);
1093
1094
9.11k
  for (int i = 0; i < (int)(sizeof(_oidc_redirect_uri_dispatch) / sizeof(oidc_redirect_uri_dispatch_t)); i++) {
1095
1096
9.11k
    entry = &_oidc_redirect_uri_dispatch[i];
1097
1098
9.11k
    if (entry->match ? (entry->match(r, c) == FALSE)
1099
9.11k
         : (oidc_util_url_has_parameter(r, entry->parameter) == FALSE))
1100
6.68k
      continue;
1101
1102
2.43k
    if ((entry->requires_auth) && (session->remote_user == NULL))
1103
1
      return HTTP_UNAUTHORIZED;
1104
1105
2.43k
    OIDC_METRICS_COUNTER_INC(r, c, entry->metric);
1106
1107
2.43k
    rc = entry->handle(r, c, session);
1108
1109
2.43k
    if (entry->timing) {
1110
771
      OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_RESPONSE);
1111
771
    }
1112
1113
2.43k
    return rc;
1114
2.43k
  }
1115
1116
  /* this is not an authorization response or logout request */
1117
1118
  /* check for "error" response */
1119
4
  if (oidc_util_url_has_parameter(r, OIDC_PROTO_ERROR)) {
1120
1121
1
    OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_PROVIDER);
1122
1123
1
    rc = oidc_response_authorization_redirect(r, c, session);
1124
1125
1
    return rc;
1126
1
  }
1127
1128
3
  OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_INVALID);
1129
1130
3
  oidc_error(
1131
3
      r, "The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR",
1132
3
      r->args);
1133
1134
  /* something went wrong */
1135
3
  return oidc_util_html_send_error(
1136
3
      r, "Invalid Request", apr_psprintf(r->pool, "The OpenID Connect callback URL received an invalid request"),
1137
3
      HTTP_INTERNAL_SERVER_ERROR);
1138
4
}
1139
1140
/*
1141
 * on a sub-request, try to recycle the authenticated user from the main/prev request;
1142
 * returns TRUE if the user could be recycled and the caller should return OK
1143
 */
1144
/*
1145
 * recycle r->user from the main (or previous, on internal redirect) request into a sub-request;
1146
 * returns FALSE when there is no user to recycle
1147
 */
1148
0
apr_byte_t oidc_subrequest_recycle_user(request_rec *r) {
1149
0
  if (r->main != NULL)
1150
0
    r->user = r->main->user;
1151
0
  else if (r->prev != NULL)
1152
0
    r->user = r->prev->user;
1153
1154
0
  if (r->user == NULL)
1155
0
    return FALSE;
1156
1157
0
  oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user);
1158
1159
0
  return TRUE;
1160
0
}
1161
1162
/* Restore parsed token state from the previous request after an internal redirect. */
1163
0
static apr_byte_t oidc_copy_tokens_from_prev_request_state(request_rec *r) {
1164
0
  const oidc_json_t *id_token = NULL;
1165
0
  const oidc_json_t *claims = NULL;
1166
0
  const char *scope = NULL;
1167
1168
0
  if (r->prev == NULL)
1169
0
    return FALSE;
1170
1171
0
  id_token = oidc_request_state_json_get(r->prev, OIDC_REQUEST_STATE_KEY_IDTOKEN);
1172
0
  if (id_token == NULL)
1173
0
    return FALSE;
1174
1175
  /* the (shallow) copies take their own JSON references, so the restored state stays valid
1176
   * independent of the previous request's cleanup order */
1177
0
  oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token);
1178
1179
0
  claims = oidc_request_state_json_get(r->prev, OIDC_REQUEST_STATE_KEY_CLAIMS);
1180
0
  if (claims != NULL)
1181
0
    oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims);
1182
1183
0
  scope = oidc_request_state_get(r->prev, OIDC_REQUEST_STATE_KEY_SCOPE);
1184
0
  if (scope != NULL)
1185
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SCOPE, apr_pstrdup(r->pool, scope));
1186
1187
0
  oidc_debug(r, "restored the token request state from the previous request on this internal redirect");
1188
1189
0
  return TRUE;
1190
0
}
1191
1192
0
static apr_byte_t oidc_check_userid_openidc_subreq(request_rec *r) {
1193
  /* this is a sub-request and we may have a session (headers will have been scrubbed and set already) */
1194
0
  if (oidc_subrequest_recycle_user(r) == FALSE)
1195
0
    return FALSE;
1196
1197
  /* apparently request state can get lost in sub-requests, so see if id_token/claims need to be restored,
1198
   * preferably from the previous request's parsed state, falling back to a full session load */
1199
0
  if ((oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN) == NULL) &&
1200
0
      (oidc_copy_tokens_from_prev_request_state(r) == FALSE)) {
1201
0
    oidc_session_t *session = NULL;
1202
0
    oidc_session_load(r, &session);
1203
0
    oidc_copy_tokens_to_request_state(r, session);
1204
0
    oidc_session_free(r, session);
1205
0
  }
1206
1207
0
  oidc_strip_cookies(r);
1208
0
  return TRUE;
1209
0
}
1210
1211
/*
1212
 * handle a request to the redirect URI: dispatch, optionally retain the session and free, then return rc
1213
 */
1214
0
static int oidc_check_userid_openidc_redirect_uri(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1215
0
  int rc = oidc_handle_redirect_uri_request(r, c, session);
1216
1217
  /* see if the session needs to be retained for the content handler phase */
1218
0
  oidc_session_t *retain = NULL;
1219
0
  apr_pool_userdata_get((void **)&retain, OIDC_USERDATA_SESSION, r->pool);
1220
0
  if (retain == NULL)
1221
0
    oidc_session_free(r, session);
1222
1223
0
  return rc;
1224
0
}
1225
1226
/*
1227
 * handle an existing authenticated session: validate, persist if updated, free and strip cookies
1228
 */
1229
0
static int oidc_check_userid_openidc_existing_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1230
0
  apr_byte_t needs_save = FALSE;
1231
0
  int rc = oidc_handle_existing_session(r, c, session, TRUE, &needs_save);
1232
0
  if ((rc == OK) && needs_save && (oidc_session_save(r, session, OIDC_SESSION_SAVE_UPDATE) == FALSE)) {
1233
0
    oidc_warn(r, "error saving session");
1234
0
    rc = HTTP_INTERNAL_SERVER_ERROR;
1235
0
  }
1236
1237
0
  oidc_session_free(r, session);
1238
0
  oidc_strip_cookies(r);
1239
1240
0
  return rc;
1241
0
}
1242
1243
/*
1244
 * main routine: handle OpenID Connect authentication
1245
 */
1246
0
static int oidc_check_userid_openidc(request_rec *r, oidc_cfg_t *c) {
1247
1248
0
  OIDC_METRICS_TIMING_START(r, c);
1249
1250
0
  if (oidc_util_url_redirect_uri(r, c) == NULL) {
1251
0
    oidc_error(r, "configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT
1252
0
            "\" but " OIDCRedirectURI " has not been set");
1253
0
    return HTTP_INTERNAL_SERVER_ERROR;
1254
0
  }
1255
1256
  /* on a sub-request, try to recycle the user from the main/prev request; fall through if it cannot */
1257
0
  if (!ap_is_initial_req(r) && (oidc_check_userid_openidc_subreq(r) == TRUE))
1258
0
    return OK;
1259
1260
  /* load the session from the request state; this will be a new "empty" session if no state exists */
1261
0
  oidc_session_t *session = NULL;
1262
0
  oidc_session_load(r, &session);
1263
1264
  /* see if the initial request is to the redirect URI; this handles potential logout too */
1265
0
  if (oidc_util_url_matches_redirect_uri(r, c) == TRUE)
1266
0
    return oidc_check_userid_openidc_redirect_uri(r, c, session);
1267
1268
  /* initial request to non-redirect URI with an existing session */
1269
0
  if (session->remote_user != NULL) {
1270
0
    int rc = oidc_check_userid_openidc_existing_session(r, c, session);
1271
0
    if (rc == OK) {
1272
0
      OIDC_METRICS_TIMING_ADD(r, c, OM_SESSION_VALID);
1273
0
    } else {
1274
0
      OIDC_METRICS_COUNTER_INC(r, c, OM_SESSION_ERROR_GENERAL);
1275
0
    }
1276
0
    return rc;
1277
0
  }
1278
1279
0
  oidc_session_free(r, session);
1280
1281
  /* no session and not an authorization or discovery response: default flow for unauthenticated users */
1282
0
  return oidc_handle_unauthenticated_user(r, c);
1283
0
}
1284
1285
/*
1286
 * main routine: handle "mixed" OIDC/OAuth authentication
1287
 */
1288
0
static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg_t *c) {
1289
1290
  /* get the bearer access token from the Authorization header */
1291
0
  const char *access_token = NULL;
1292
0
  if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE) {
1293
1294
0
    r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_OAUTH20);
1295
0
    return oidc_oauth_check_userid(r, c, access_token);
1296
0
  }
1297
1298
0
  if (r->method_number == M_OPTIONS) {
1299
    /* see the identical case in oidc_oauth_check_userid(): a CORS preflight is let
1300
     * through unauthenticated, so its OIDC_* headers are the client's own */
1301
0
    oidc_scrub_headers(r);
1302
0
    r->user = "";
1303
0
    return OK;
1304
0
  }
1305
1306
  /* no bearer token found: then treat this as a regular OIDC browser request */
1307
0
  r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_CONNECT);
1308
0
  return oidc_check_userid_openidc(r, c);
1309
0
}
1310
1311
0
int oidc_fixups(request_rec *r) {
1312
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1313
0
  if (oidc_enabled(r, c) == TRUE) {
1314
0
    OIDC_METRICS_TIMING_REQUEST_ADD(r, c, OM_MOD_AUTH_OPENIDC);
1315
0
    return OK;
1316
0
  }
1317
0
  return DECLINED;
1318
0
}
1319
1320
/*
1321
 * generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines
1322
 */
1323
0
int oidc_check_user_id(request_rec *r) {
1324
1325
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1326
0
  int rv = DECLINED;
1327
1328
0
  OIDC_METRICS_TIMING_REQUEST_START(r, c);
1329
1330
  /* log some stuff about the incoming HTTP request */
1331
0
  oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d", r->parsed_uri.path, r->args,
1332
0
       ap_is_initial_req(r));
1333
1334
0
  if (oidc_enabled(r, c) == FALSE) {
1335
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_DECLINED);
1336
0
    return DECLINED;
1337
0
  }
1338
1339
0
  oidc_util_set_trace_parent(r, c, NULL);
1340
1341
0
  OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_MOD_AUTH_OPENIDC);
1342
1343
  /* see if we've configured OpenID Connect user authentication for this request */
1344
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) {
1345
1346
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OPENID_CONNECT);
1347
0
    r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r));
1348
0
    rv = oidc_check_userid_openidc(r, c);
1349
1350
    /* see if we've configured OAuth 2.0 access control for this request */
1351
0
  } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
1352
1353
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OAUTH20);
1354
0
    r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r));
1355
0
    rv = oidc_oauth_check_userid(r, c, NULL);
1356
1357
    /* see if we've configured "mixed mode" for this request */
1358
0
  } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0) {
1359
1360
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_AUTH_OPENIDC);
1361
0
    rv = oidc_check_mixed_userid_oauth(r, c);
1362
0
  }
1363
1364
0
  return rv;
1365
0
}
1366
1367
/*
1368
 * check of mod_auth_openidc needs to handle this request
1369
 */
1370
139
apr_byte_t oidc_enabled(request_rec *r, oidc_cfg_t *c) {
1371
1372
139
  if (ap_auth_type(r) == NULL)
1373
0
    return FALSE;
1374
1375
139
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0)
1376
139
    return TRUE;
1377
1378
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0)
1379
0
    return TRUE;
1380
1381
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0)
1382
0
    return TRUE;
1383
1384
0
  return FALSE;
1385
0
}
1386
1387
/*
1388
 * SSL initialization magic copied from mod_auth_cas
1389
 */
1390
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1391
1392
static apr_thread_mutex_t **ssl_locks;
1393
static int ssl_num_locks;
1394
1395
static void oidc_ssl_locking_callback(int mode, int type, const char *file, int line) {
1396
  if (type < ssl_num_locks) {
1397
    if (mode & CRYPTO_LOCK)
1398
      apr_thread_mutex_lock(ssl_locks[type]);
1399
    else
1400
      apr_thread_mutex_unlock(ssl_locks[type]);
1401
  }
1402
}
1403
1404
#ifdef OPENSSL_NO_THREADID
1405
static unsigned long oidc_ssl_id_callback(void) {
1406
  return (unsigned long)apr_os_thread_current();
1407
}
1408
#else
1409
static void oidc_ssl_id_callback(CRYPTO_THREADID *id) {
1410
  CRYPTO_THREADID_set_numeric(id, (unsigned long)apr_os_thread_current());
1411
}
1412
#endif /* OPENSSL_NO_THREADID */
1413
1414
#endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */
1415
1416
/*
1417
 * cleanup resources allocated in a process
1418
 */
1419
0
static apr_status_t oidc_process_cleanup(void *data) {
1420
1421
0
  server_rec *sp = (server_rec *)data;
1422
0
  while (sp != NULL) {
1423
0
    oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1424
0
    oidc_cfg_process_cleanup(cfg, sp);
1425
0
    sp = sp->next;
1426
0
  }
1427
1428
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1429
  if (CRYPTO_get_locking_callback() == oidc_ssl_locking_callback)
1430
    CRYPTO_set_locking_callback(NULL);
1431
#ifdef OPENSSL_NO_THREADID
1432
  if (CRYPTO_get_id_callback() == oidc_ssl_id_callback)
1433
    CRYPTO_set_id_callback(NULL);
1434
#else
1435
  if (CRYPTO_THREADID_get_callback() == oidc_ssl_id_callback)
1436
    CRYPTO_THREADID_set_callback(NULL);
1437
#endif /* OPENSSL_NO_THREADID */
1438
1439
#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */
1440
1441
0
  EVP_cleanup();
1442
0
  oidc_http_cleanup();
1443
1444
0
  ap_log_error(APLOG_MARK, APLOG_INFO, 0, (server_rec *)data, "%s - shutdown", NAMEVERSION);
1445
1446
0
  return APR_SUCCESS;
1447
0
}
1448
1449
/*
1450
 * handler that is called (twice) after the configuration phase; check if everything is OK
1451
 */
1452
0
static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s) {
1453
0
  const char *userdata_key = "oidc_post_config";
1454
0
  void *data = NULL;
1455
1456
  /* Since the post_config hook is invoked twice (once
1457
   * for 'sanity checking' of the config and once for
1458
   * the actual server launch, we have to use a hack
1459
   * to not run twice
1460
   */
1461
0
  apr_pool_userdata_get(&data, userdata_key, s->process->pool);
1462
0
  if (data == NULL) {
1463
0
    apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool);
1464
0
    return OK;
1465
0
  }
1466
1467
0
#ifdef USE_MEMCACHE
1468
0
#define _OIDC_USE_MEMCACHE "yes"
1469
#else
1470
#define _OIDC_USE_MEMCACHE "no"
1471
#endif
1472
1473
#ifdef USE_LIBHIREDIS
1474
#define _OIDC_USE_REDIS "yes"
1475
#else
1476
0
#define _OIDC_USE_REDIS "no"
1477
0
#endif
1478
1479
#ifdef USE_LIBJQ
1480
#define _OIDC_USE_JQ "yes"
1481
#else
1482
0
#define _OIDC_USE_JQ "no"
1483
0
#endif
1484
1485
0
  ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
1486
0
         "%s - init - cjose %s, %s, EC=%s, GCM=%s, Memcache=%s, Redis=%s, JQ=%s", NAMEVERSION,
1487
0
         oidc_jose_version(), oidc_util_openssl_version(s->process->pool),
1488
0
         OIDC_JOSE_EC_SUPPORT ? "yes" : "no", OIDC_JOSE_GCM_SUPPORT ? "yes" : "no", _OIDC_USE_MEMCACHE,
1489
0
         _OIDC_USE_REDIS, _OIDC_USE_JQ);
1490
1491
0
  oidc_http_init();
1492
1493
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1494
  ssl_num_locks = CRYPTO_num_locks();
1495
  ssl_locks = apr_pcalloc(s->process->pool, ssl_num_locks * sizeof(*ssl_locks));
1496
1497
  int i;
1498
  for (i = 0; i < ssl_num_locks; i++)
1499
    apr_thread_mutex_create(&(ssl_locks[i]), APR_THREAD_MUTEX_DEFAULT, s->process->pool);
1500
1501
#ifdef OPENSSL_NO_THREADID
1502
  if (CRYPTO_get_locking_callback() == NULL && CRYPTO_get_id_callback() == NULL) {
1503
    CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
1504
    CRYPTO_set_id_callback(oidc_ssl_id_callback);
1505
  }
1506
#else
1507
  if (CRYPTO_get_locking_callback() == NULL && CRYPTO_THREADID_get_callback() == NULL) {
1508
    CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
1509
    CRYPTO_THREADID_set_callback(oidc_ssl_id_callback);
1510
  }
1511
#endif /* OPENSSL_NO_THREADID */
1512
1513
#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */
1514
1515
0
  apr_pool_cleanup_register(pool, s, oidc_process_cleanup, apr_pool_cleanup_null);
1516
1517
  /* must come after the oidc_process_cleanup registration: pool cleanups run last-in-first-out
1518
   * and the pooled easy handles have to be cleaned up before that cleanup's
1519
   * curl_global_cleanup() tears down libcurl underneath them */
1520
0
  oidc_http_curl_pool_init(pool);
1521
1522
0
  if (oidc_cfg_dir_post_config(s) != OK)
1523
0
    return HTTP_INTERNAL_SERVER_ERROR;
1524
1525
0
  server_rec *sp = s;
1526
0
  while (sp != NULL) {
1527
0
    oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1528
0
    if (oidc_cfg_post_config(pool, cfg, sp) != OK)
1529
0
      return HTTP_INTERNAL_SERVER_ERROR;
1530
0
    sp = sp->next;
1531
0
  }
1532
1533
0
  return oidc_cfg_check_vhosts(pool, s);
1534
0
}
1535
1536
/*
1537
 * parse an Apache expression in the configured require value
1538
 */
1539
0
static const char *oidc_parse_config(cmd_parms *cmd, const char *require_line, const void **parsed_require_line) {
1540
0
  const char *expr_err = NULL;
1541
0
  const ap_expr_info_t *expr;
1542
1543
0
  expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL);
1544
1545
0
  if (expr_err)
1546
0
    return apr_pstrcat(cmd->temp_pool, "Cannot parse expression in require line: ", expr_err, NULL);
1547
1548
0
  *parsed_require_line = expr;
1549
1550
0
  return NULL;
1551
0
}
1552
1553
static const authz_provider oidc_authz_claim_provider = {
1554
    &oidc_authz_24_checker_claim,
1555
    &oidc_parse_config,
1556
};
1557
#ifdef USE_LIBJQ
1558
static const authz_provider oidc_authz_claims_expr_provider = {
1559
    &oidc_authz_24_checker_claims_expr,
1560
    NULL,
1561
};
1562
#endif
1563
1564
/*
1565
 * initialize cache context in child process if required
1566
 */
1567
0
static void oidc_child_init(apr_pool_t *p, server_rec *s) {
1568
0
  server_rec *sp = s;
1569
  /* drop any curl handles inherited over fork(): their connections share descriptors and
1570
   * TLS state with the parent process */
1571
0
  oidc_http_curl_pool_child_init();
1572
0
  while (sp != NULL) {
1573
0
    const oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1574
0
    oidc_cfg_child_init(p, cfg, sp);
1575
0
    sp = sp->next;
1576
0
  }
1577
0
}
1578
1579
static const char oidcFilterName[] = "oidc_filter_in_filter";
1580
1581
/*
1582
 * add filter for inserting POST data
1583
 */
1584
0
static void oidc_filter_in_insert_filter(request_rec *r) {
1585
1586
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1587
1588
0
  if (oidc_enabled(r, c) == FALSE)
1589
0
    return;
1590
1591
0
  if (ap_is_initial_req(r) == 0)
1592
0
    return;
1593
1594
0
  apr_table_t *userdata_post_params = NULL;
1595
0
  apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, r->pool);
1596
0
  if (userdata_post_params == NULL)
1597
0
    return;
1598
1599
0
  ap_add_input_filter(oidcFilterName, NULL, r, r->connection);
1600
0
}
1601
1602
typedef struct oidc_filter_in_context {
1603
  apr_bucket_brigade *pbbTmp;
1604
  apr_size_t nbytes;
1605
} oidc_filter_in_context;
1606
1607
/*
1608
 * append a bucket with the captured POST parameters as form-encoded data to the brigade and
1609
 * update the Content-Length request header accordingly; no-op if there are no captured parameters
1610
 */
1611
static void oidc_filter_in_filter_append_post_params(ap_filter_t *f, apr_bucket_brigade *brigade,
1612
0
                 oidc_filter_in_context *ctx) {
1613
1614
0
  apr_table_t *userdata_post_params = NULL;
1615
0
  apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, f->r->pool);
1616
0
  if (userdata_post_params == NULL)
1617
0
    return;
1618
1619
0
  const char *buf = apr_psprintf(f->r->pool, "%s%s", ctx->nbytes > 0 ? "&" : "",
1620
0
               oidc_http_form_encoded_data(f->r, userdata_post_params));
1621
0
  apr_bucket *b_out = apr_bucket_heap_create(buf, _oidc_strlen(buf), 0, f->r->connection->bucket_alloc);
1622
1623
0
  APR_BRIGADE_INSERT_TAIL(brigade, b_out);
1624
1625
0
  ctx->nbytes += _oidc_strlen(buf);
1626
1627
0
  if (oidc_http_hdr_in_content_length_get(f->r) != NULL)
1628
0
    oidc_http_hdr_in_set(f->r, OIDC_HTTP_HDR_CONTENT_LENGTH,
1629
0
             apr_psprintf(f->r->pool, "%ld", (long)ctx->nbytes));
1630
1631
0
  apr_pool_userdata_set(NULL, OIDC_USERDATA_POST_PARAMS_KEY, NULL, f->r->pool);
1632
0
}
1633
1634
/*
1635
 * execute filter for inserting POST data
1636
 */
1637
static apr_status_t oidc_filter_in_filter(ap_filter_t *f, apr_bucket_brigade *brigade, ap_input_mode_t mode,
1638
0
            apr_read_type_e block, apr_off_t nbytes) {
1639
0
  oidc_filter_in_context *ctx = NULL;
1640
0
  apr_bucket *b_in = NULL;
1641
0
  apr_status_t rc = APR_SUCCESS;
1642
1643
0
  if (!(ctx = f->ctx)) {
1644
0
    ctx = apr_palloc(f->r->pool, sizeof *ctx);
1645
0
    f->ctx = ctx;
1646
0
    ctx->pbbTmp = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
1647
0
    ctx->nbytes = 0;
1648
0
  }
1649
1650
0
  if (APR_BRIGADE_EMPTY(ctx->pbbTmp)) {
1651
0
    rc = ap_get_brigade(f->next, ctx->pbbTmp, mode, block, nbytes);
1652
1653
0
    if (mode == AP_MODE_EATCRLF || rc != APR_SUCCESS)
1654
0
      return rc;
1655
0
  }
1656
1657
0
  while (!APR_BRIGADE_EMPTY(ctx->pbbTmp)) {
1658
1659
0
    b_in = APR_BRIGADE_FIRST(ctx->pbbTmp);
1660
0
    APR_BUCKET_REMOVE(b_in);
1661
1662
0
    if (APR_BUCKET_IS_EOS(b_in)) {
1663
0
      oidc_filter_in_filter_append_post_params(f, brigade, ctx);
1664
0
      APR_BRIGADE_INSERT_TAIL(brigade, b_in);
1665
0
      break;
1666
0
    }
1667
1668
0
    APR_BRIGADE_INSERT_TAIL(brigade, b_in);
1669
0
    ctx->nbytes += b_in->length;
1670
0
  }
1671
1672
0
  return rc;
1673
0
}
1674
1675
/*
1676
 * register our authentication and authorization functions
1677
 */
1678
0
static void oidc_register_hooks(apr_pool_t *pool) {
1679
0
  oidc_pre_config_init();
1680
0
  ap_hook_post_config(oidc_post_config, NULL, NULL, APR_HOOK_LAST);
1681
0
  ap_hook_child_init(oidc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1682
0
  ap_hook_fixups(oidc_fixups, NULL, NULL, APR_HOOK_MIDDLE);
1683
0
  static const char *const proxySucc[] = {"mod_proxy.c", NULL};
1684
0
  ap_hook_handler(oidc_content_handler, NULL, proxySucc, APR_HOOK_FIRST);
1685
0
  ap_hook_insert_filter(oidc_filter_in_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
1686
0
  ap_register_input_filter(oidcFilterName, oidc_filter_in_filter, NULL, AP_FTYPE_RESOURCE);
1687
0
  ap_hook_check_authn(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF);
1688
0
  ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider,
1689
0
          AP_AUTH_INTERNAL_PER_CONF);
1690
#ifdef USE_LIBJQ
1691
  ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0",
1692
          &oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF);
1693
#endif
1694
0
}
1695
1696
// clang-format off
1697
module AP_MODULE_DECLARE_DATA auth_openidc_module = {
1698
    STANDARD20_MODULE_STUFF,
1699
  oidc_cfg_dir_config_create,
1700
  oidc_cfg_dir_config_merge,
1701
  oidc_cfg_server_create,
1702
  oidc_cfg_server_merge,
1703
  oidc_cfg_cmds,
1704
  oidc_register_hooks
1705
};
1706
// clang-format on