Coverage Report

Created: 2026-09-03 06:52

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.56k
static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix, apr_hash_t *scrub) {
78
79
1.56k
  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.56k
  const apr_array_header_t *const h = apr_table_elts(r->headers_in);
83
84
  /* table to keep the non-suspicious headers */
85
1.56k
  apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts);
86
87
  /* loop over the incoming HTTP headers */
88
1.56k
  const apr_table_entry_t *const e = (const apr_table_entry_t *)h->elts;
89
6.21k
  for (int i = 0; i < h->nelts; i++) {
90
4.64k
    const char *const k = e[i].key;
91
92
    /* is this header's name equivalent to a header that needs scrubbing? */
93
4.64k
    const char *hdr = (k != NULL) && (scrub != NULL) ? apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL;
94
4.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
4.64k
    const int prefix_matches =
104
4.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
4.64k
    if (!prefix_matches && !header_matches) {
108
3.08k
      apr_table_addn(clean_headers, k, e[i].val);
109
3.08k
    } else {
110
1.56k
      oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k, e[i].val);
111
1.56k
    }
112
4.64k
  }
113
114
  /* overwrite the incoming headers with the cleaned result */
115
1.56k
  r->headers_in = clean_headers;
116
1.56k
}
117
118
/*
119
 * scrub all mod_auth_openidc related headers
120
 */
121
1.56k
void oidc_scrub_headers(request_rec *r) {
122
1.56k
  const oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
123
124
1.56k
  const char *prefix = oidc_cfg_claim_prefix_get(cfg);
125
1.56k
  apr_hash_t *hdrs = apr_hash_make(r->pool);
126
127
1.56k
  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.56k
  const char *authn_hdr = oidc_cfg_dir_authn_header_get(r);
137
1.56k
  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.56k
  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.56k
  if (_oidc_strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix) {
150
0
    oidc_scrub_request_headers(r, prefix, NULL);
151
0
  }
152
1.56k
}
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
1.12k
void oidc_strip_cookies(request_rec *r) {
171
172
1.12k
  const apr_array_header_t *strip = oidc_cfg_dir_strip_cookies_get(r);
173
1.12k
  char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r));
174
175
1.12k
  if ((cookies == NULL) || (strip == NULL))
176
1.12k
    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
21.1k
apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg_t *c, oidc_provider_t **provider) {
235
236
21.1k
  oidc_json_t *j_provider = NULL;
237
21.1k
  char *s_json = NULL;
238
239
  /* see if we should configure a static provider based on external (cached) metadata */
240
21.1k
  if ((oidc_cfg_metadata_dir_get(c) != NULL) ||
241
21.1k
      (oidc_cfg_provider_metadata_url_get(oidc_cfg_provider_get(c)) == NULL)) {
242
21.1k
    *provider = oidc_cfg_provider_get(c);
243
21.1k
    return TRUE;
244
21.1k
  }
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
11.6k
                apr_byte_t allow_discovery) {
291
292
  /* by default we'll assume that we're dealing with a single statically configured OP */
293
11.6k
  oidc_provider_t *provider = NULL;
294
11.6k
  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
11.6k
  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
11.6k
  return provider;
306
11.6k
}
307
308
/*
309
 * return the HTTP method being called: only for POST data persistence purposes
310
 */
311
5.12k
const char *oidc_original_request_method(request_rec *r, oidc_cfg_t *cfg, apr_byte_t handle_discovery_response) {
312
5.12k
  const char *method = OIDC_METHOD_GET;
313
314
5.12k
  char *m = NULL;
315
5.12k
  if ((handle_discovery_response == TRUE) && (oidc_util_url_matches_redirect_uri(r, cfg)) &&
316
542
      (oidc_is_discovery_response(r, cfg))) {
317
528
    oidc_util_url_parameter_get(r, OIDC_DISC_RM_PARAM, &m);
318
528
    if (m != NULL)
319
104
      method = apr_pstrdup(r->pool, m);
320
4.59k
  } 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
4.59k
    if (oidc_cfg_dir_preserve_post_get(r) == 0)
328
3.82k
      return OIDC_METHOD_GET;
329
330
771
    const char *content_type = oidc_http_hdr_in_content_type_get(r);
331
771
    if ((r->method_number == M_POST) && (content_type != NULL) &&
332
771
        (_oidc_strcmp(content_type, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED) == 0))
333
771
      method = OIDC_METHOD_FORM_POST;
334
771
  }
335
336
1.29k
  oidc_debug(r, "return: %s", method);
337
338
1.29k
  return method;
339
5.12k
}
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
988
void oidc_log_session_expires(request_rec *r, const char *msg, apr_time_t session_expires) {
365
988
  char buf[APR_RFC822_DATE_LEN + 1];
366
988
  apr_rfc822_date(buf, session_expires);
367
988
  oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf,
368
988
       apr_time_sec(session_expires - apr_time_now()));
369
988
}
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
51
apr_byte_t oidc_check_cookie_domain(request_rec *r, const oidc_cfg_t *cfg, const oidc_session_t *session) {
466
51
  const char *c_cookie_domain = oidc_cfg_cookie_domain_get(cfg)
467
51
            ? oidc_cfg_cookie_domain_get(cfg)
468
51
            : oidc_util_url_cur_host(r, oidc_cfg_x_forwarded_headers_get(cfg));
469
51
  const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session);
470
51
  if ((s_cookie_domain == NULL) || (_oidc_strnatcasecmp(c_cookie_domain, s_cookie_domain) != 0)) {
471
51
    oidc_warn(r,
472
51
        "aborting: detected attempt to play cookie against a different domain/host than issued for! "
473
51
        "(issued=%s, current=%s)",
474
51
        s_cookie_domain, c_cookie_domain);
475
51
    return FALSE;
476
51
  }
477
478
0
  return TRUE;
479
51
}
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
375
            oidc_provider_t **provider) {
486
487
375
  oidc_debug(r, "enter");
488
489
  /* get the issuer value from the session state */
490
375
  const char *issuer = oidc_session_get_issuer(r, session);
491
375
  if (issuer == NULL) {
492
176
    oidc_warn(r, "empty or invalid session: no issuer found");
493
176
    return FALSE;
494
176
  }
495
496
  /* get the provider info associated with the issuer value */
497
199
  oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
498
199
  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
199
  *provider = p;
504
505
199
  return TRUE;
506
199
}
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
51
          apr_byte_t extend_session, apr_byte_t *needs_save) {
648
649
51
  apr_byte_t rv = FALSE;
650
51
  int rc = OK;
651
652
51
  oidc_debug(r, "enter");
653
654
  /* set the user in the main request for further (incl. sub-request) processing */
655
51
  r->user = apr_pstrdup(r->pool, session->remote_user);
656
51
  oidc_debug(r, "set remote_user to \"%s\" in existing session \"%s\"", r->user, session->uuid);
657
658
  /* get the header name in which the remote user name needs to be passed */
659
51
  const char *authn_header = oidc_cfg_dir_authn_header_get(r);
660
661
51
  oidc_appinfo_pass_in_t pass_in = oidc_cfg_dir_pass_info_in_get(r);
662
51
  oidc_appinfo_encoding_t encoding = oidc_cfg_dir_pass_info_encoding_get(r);
663
664
  /* verify current cookie domain against issued cookie domain */
665
51
  if (oidc_check_cookie_domain(r, cfg, session) == FALSE) {
666
51
    *needs_save = FALSE;
667
51
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_COOKIE_DOMAIN);
668
51
    return HTTP_UNAUTHORIZED;
669
51
  }
670
671
  /*
672
   * we're going to pass the information that we have to the application,
673
   * but first we need to scrub the headers that we're going to use for security reasons
674
   * NB: need it before oidc_check_max_session_duration since OIDCUnAuthAction pass may be set
675
   */
676
0
  oidc_scrub_headers(r);
677
678
  /* check if the maximum session duration was exceeded */
679
0
  if (oidc_check_max_session_duration(r, cfg, session, &rc) == FALSE) {
680
0
    *needs_save = FALSE;
681
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_EXPIRED);
682
    // NB: rc was set (e.g. to a 302 auth redirect) by the call to oidc_check_max_session_duration
683
0
    return rc;
684
0
  }
685
686
0
  if (extend_session) {
687
688
    /* if needed, refresh the access token */
689
0
    rv = oidc_refresh_access_token_before_expiry(
690
0
        r, cfg, session, oidc_cfg_dir_refresh_access_token_before_expiry_get(r), needs_save);
691
0
    if (rv == FALSE) {
692
0
      *needs_save = FALSE;
693
0
      oidc_debug(r, "dir_action_on_error_refresh: %d", oidc_cfg_dir_action_on_error_refresh_get(r));
694
0
      OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_ACCESS_TOKEN);
695
0
      return oidc_handle_session_refresh_error(r, cfg, session,
696
0
                 oidc_cfg_dir_action_on_error_refresh_get(r));
697
0
    }
698
699
    /* if needed, refresh claims from the user info endpoint */
700
0
    rv = oidc_userinfo_refresh_claims(r, cfg, session, needs_save);
701
0
    if (rv == FALSE) {
702
0
      *needs_save = FALSE;
703
0
      oidc_debug(r, "action_on_userinfo_error: %d", oidc_cfg_action_on_userinfo_error_get(cfg));
704
0
      OIDC_METRICS_COUNTER_INC(r, cfg, OM_SESSION_ERROR_REFRESH_USERINFO);
705
0
      return oidc_handle_session_refresh_error(r, cfg, session,
706
0
                 oidc_cfg_action_on_userinfo_error_get(cfg));
707
0
    }
708
0
  }
709
710
  /* set the user authentication HTTP header if set and required */
711
0
  if ((r->user != NULL) && (authn_header != NULL))
712
0
    oidc_http_hdr_in_set(r, authn_header, r->user);
713
714
  /* copy id_token and claims from session to request state and obtain their values */
715
0
  oidc_copy_tokens_to_request_state(r, session);
716
717
  /* pass the at, rt and at expiry to the application, possibly update the session expiry */
718
0
  oidc_session_pass_tokens(r, cfg, session, extend_session, needs_save);
719
720
  /* pass ID token and claims */
721
0
  oidc_idtoken_pass_as(r, cfg, session, pass_in, encoding);
722
  /* pass userinfo claims */
723
0
  oidc_userinfo_pass_as(r, cfg, session, pass_in, encoding);
724
725
  /* return "user authenticated" status */
726
0
  return OK;
727
0
}
728
729
/*
730
 * get the r->user for this request based on the configuration for OIDC/OAuth
731
 */
732
apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *reg_exp, const char *replace,
733
2.38k
        const oidc_json_t *json, char **request_user) {
734
735
  /* get the claim value from the JSON object */
736
2.38k
  const oidc_json_t *username = oidc_json_object_get(json, claim_name);
737
2.38k
  if ((username == NULL) || (!oidc_json_is_string(username))) {
738
270
    oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name);
739
270
    return FALSE;
740
270
  }
741
742
2.11k
  *request_user = apr_pstrdup(r->pool, oidc_json_string_value(username));
743
744
2.11k
  if (reg_exp != NULL) {
745
746
0
    char *error_str = NULL;
747
748
0
    if (replace == NULL) {
749
750
0
      if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp, request_user, &error_str) ==
751
0
          FALSE) {
752
0
        oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str);
753
0
        *request_user = NULL;
754
0
        return FALSE;
755
0
      }
756
757
0
    } else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp, replace, request_user,
758
0
                   &error_str) == FALSE) {
759
760
0
      oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str);
761
0
      *request_user = NULL;
762
0
      return FALSE;
763
0
    }
764
0
  }
765
766
2.11k
  return TRUE;
767
2.11k
}
768
769
6.13k
#define OIDC_MAX_URL_LENGTH (8192 * 2)
770
771
/*
772
 * fill the err_str/err_desc out-params, log the error and return FALSE
773
 */
774
static apr_byte_t oidc_validate_redirect_url_fail(request_rec *r, char **err_str, char **err_desc, const char *str,
775
956
              const char *desc) {
776
956
  *err_str = apr_pstrdup(r->pool, str);
777
956
  *err_desc = apr_pstrdup(r->pool, desc);
778
956
  oidc_error(r, "%s: %s", *err_str, *err_desc);
779
956
  return FALSE;
780
956
}
781
782
/*
783
 * verify the URL matches one of the OIDCRedirectURLsAllowed regexes
784
 */
785
static apr_byte_t oidc_validate_redirect_url_allowed(request_rec *r, apr_hash_t *allowed, const char *url,
786
167
                 char **err_str, char **err_desc) {
787
167
  const char *c_host = NULL;
788
487
  for (apr_hash_index_t *hi = apr_hash_first(NULL, allowed); hi; hi = apr_hash_next(hi)) {
789
332
    apr_hash_this(hi, (const void **)&c_host, NULL, NULL);
790
332
    if (oidc_util_regexp_first_match(r->pool, url, c_host, NULL, err_str) == TRUE)
791
12
      return TRUE;
792
332
  }
793
155
  return oidc_validate_redirect_url_fail(
794
155
      r, err_str, err_desc, "URL not allowed",
795
155
      apr_psprintf(r->pool, "value does not match the list of allowed redirect URLs: %s", url));
796
167
}
797
798
/*
799
 * verify the URL hostname matches the hostname of the current request
800
 */
801
static apr_byte_t oidc_validate_redirect_url_host(request_rec *r, const oidc_cfg_t *c, apr_uri_t *uri, char **err_str,
802
5.09k
              char **err_desc) {
803
5.09k
  const char *c_host = oidc_util_url_cur_host(r, oidc_cfg_x_forwarded_headers_get(c));
804
  /* IPv6 literals need to be wrapped in brackets to compare with the current hostname */
805
5.09k
  const char *url_ipv6_aware =
806
5.09k
      strchr(uri->hostname, ':') ? apr_pstrcat(r->pool, "[", uri->hostname, "]", NULL) : uri->hostname;
807
5.09k
  if (_oidc_strnatcasecmp(c_host, url_ipv6_aware) == 0)
808
4.89k
    return TRUE;
809
200
  return oidc_validate_redirect_url_fail(
810
200
      r, err_str, err_desc, "Invalid Request",
811
200
      apr_psprintf(r->pool, "URL value \"%s\" does not match the hostname of the current request \"%s\"",
812
200
       apr_uri_unparse(r->pool, uri, 0), c_host));
813
5.09k
}
814
815
/*
816
 * for hostname-less URLs, require the URL to be a safe relative path
817
 */
818
static apr_byte_t oidc_validate_redirect_url_relative(request_rec *r, const char *url, char **err_str,
819
670
                  char **err_desc) {
820
670
  if (_oidc_strstr(url, "/") != url)
821
400
    return oidc_validate_redirect_url_fail(
822
400
        r, err_str, err_desc, "Malformed URL",
823
400
        apr_psprintf(
824
400
      r->pool,
825
400
      "No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s", url));
826
270
  if (_oidc_strstr(url, "//") == url)
827
34
    return oidc_validate_redirect_url_fail(
828
34
        r, err_str, err_desc, "Malformed URL",
829
34
        apr_psprintf(r->pool, "No hostname was parsed and starting with '//': %s", url));
830
236
  if (_oidc_strstr(url, "/\\") == url)
831
0
    return oidc_validate_redirect_url_fail(
832
0
        r, err_str, err_desc, "Malformed URL",
833
0
        apr_psprintf(r->pool, "No hostname was parsed and starting with '/\\': %s", url));
834
236
  return TRUE;
835
236
}
836
837
/*
838
 * reject the URL when it contains characters used for HTTP header splitting or other smuggling tricks
839
 */
840
/*
841
 * substrings that must not occur in a URL that is redirected to: URL-smuggling and scheme-injection
842
 * vectors such as (percent-encoded) tab/slash/backslash separators, embedded scheme prefixes, and
843
 * CJK look-alike separator characters; each entry states whether it is matched case-insensitively
844
 */
845
static const struct {
846
  const char *needle;
847
  apr_byte_t case_insensitive;
848
} _oidc_redirect_url_illegal_substrings[] = {
849
    {"/%09", FALSE},  {"/%2f", TRUE},  {"/\t", FALSE},   {"/%68", FALSE},
850
    {"/http:", TRUE}, {"/https:", TRUE}, {"/javascript:", TRUE}, {"%01javascript:", TRUE},
851
    {"/〱", FALSE},   {"/〵", FALSE},  {"/ゝ", FALSE},  {"/ー", FALSE},
852
    {"/ー", FALSE},    {"/<", FALSE},   {"/%5c", FALSE},  {"/\\", FALSE},
853
    {NULL, FALSE},
854
};
855
856
5.21k
static apr_byte_t oidc_validate_redirect_url_chars(request_rec *r, const char *url, char **err_str, char **err_desc) {
857
5.21k
  if ((_oidc_strstr(url, "\n") != NULL) || (_oidc_strstr(url, "\r") != NULL))
858
27
    return oidc_validate_redirect_url_fail(
859
27
        r, err_str, err_desc, "Invalid URL",
860
27
        apr_psprintf(r->pool, "URL value \"%s\" contains illegal \"\n\" or \"\r\" character(s)", url));
861
862
87.5k
  for (int i = 0; _oidc_redirect_url_illegal_substrings[i].needle != NULL; i++) {
863
82.3k
    const char *needle = _oidc_redirect_url_illegal_substrings[i].needle;
864
82.3k
    const char *found = _oidc_redirect_url_illegal_substrings[i].case_insensitive
865
82.3k
          ? oidc_util_strcasestr(url, needle)
866
82.3k
          : _oidc_strstr(url, needle);
867
82.3k
    if (found != NULL)
868
60
      return oidc_validate_redirect_url_fail(
869
60
          r, err_str, err_desc, "Invalid URL",
870
60
          apr_psprintf(r->pool, "URL value \"%s\" contains illegal character(s)", url));
871
82.3k
  }
872
873
5.12k
  return TRUE;
874
5.18k
}
875
876
/*
877
 * avoid cross site request forgery on the redirect_to_url
878
 */
879
apr_byte_t oidc_validate_redirect_url(request_rec *r, const oidc_cfg_t *c, const char *redirect_to_url,
880
6.08k
              oidc_redirect_url_scope_t scope, char **err_str, char **err_desc) {
881
6.08k
  apr_uri_t uri;
882
6.08k
  if (redirect_to_url == NULL)
883
0
    return oidc_validate_redirect_url_fail(r, err_str, err_desc, "Invalid URL", "URL value is NULL");
884
6.08k
  if (_oidc_strlen(redirect_to_url) > OIDC_MAX_URL_LENGTH)
885
50
    return oidc_validate_redirect_url_fail(
886
50
        r, err_str, err_desc, "URL too long",
887
50
        apr_psprintf(r->pool, "URL value exceeds the maximum length of %d bytes", OIDC_MAX_URL_LENGTH));
888
6.03k
  char *url = apr_pstrdup(r->pool, redirect_to_url);
889
890
  // replace potentially harmful backslashes with forward slashes
891
2.14M
  for (size_t i = 0; i < _oidc_strlen(url); i++)
892
2.14M
    if (url[i] == '\\')
893
40.9k
      url[i] = '/';
894
895
6.03k
  if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS)
896
30
    return oidc_validate_redirect_url_fail(r, err_str, err_desc, "Malformed URL",
897
30
                   apr_psprintf(r->pool, "not a valid URL value: %s", url));
898
899
6.00k
  if (oidc_cfg_redirect_urls_allowed_get(c) != NULL) {
900
167
    if (oidc_validate_redirect_url_allowed(r, oidc_cfg_redirect_urls_allowed_get(c), url, err_str,
901
167
                   err_desc) == FALSE)
902
155
      return FALSE;
903
5.83k
  } else if ((uri.hostname != NULL) && (scope == OIDC_REDIRECT_URL_SAME_HOST) &&
904
5.09k
       (oidc_validate_redirect_url_host(r, c, &uri, err_str, err_desc) == FALSE)) {
905
200
    return FALSE;
906
200
  }
907
908
5.64k
  if ((uri.hostname == NULL) && (oidc_validate_redirect_url_relative(r, url, err_str, err_desc) == FALSE))
909
434
    return FALSE;
910
911
  /* validate the URL to prevent HTTP header splitting */
912
5.21k
  return oidc_validate_redirect_url_chars(r, url, err_str, err_desc);
913
5.64k
}
914
915
/*
916
 * return the Javascript code used to handle an Implicit grant type
917
 * i.e. that posts the data returned by the OP in the URL fragment to the OIDCRedirectURI
918
 */
919
11
static int oidc_javascript_implicit(request_rec *r, oidc_cfg_t *c) {
920
921
11
  oidc_debug(r, "enter");
922
923
11
  const char *java_script =
924
11
      "    <script type=\"text/javascript\">\n"
925
11
      "      function postOnLoad() {\n"
926
11
      "        encoded = location.hash.substring(1).split('&');\n"
927
11
      "        for (i = 0; i < encoded.length; i++) {\n"
928
11
      "          encoded[i].replace(/\\+/g, ' ');\n"
929
11
      "          var n = encoded[i].indexOf('=');\n"
930
11
      "          var input = document.createElement('input');\n"
931
11
      "          input.type = 'hidden';\n"
932
11
      "          input.name = decodeURIComponent(encoded[i].substring(0, n));\n"
933
11
      "          input.value = decodeURIComponent(encoded[i].substring(n+1));\n"
934
11
      "          document.forms[0].appendChild(input);\n"
935
11
      "        }\n"
936
11
      "        document.forms[0].action = window.location.href.substr(0, window.location.href.indexOf('#'));\n"
937
11
      "        HTMLFormElement.prototype.submit.call(document.forms[0]);\n"
938
11
      "      }\n"
939
11
      "    </script>\n";
940
941
11
  const char *html_body = "    <p>Submitting...</p>\n"
942
11
        "    <form method=\"post\" action=\"\">\n"
943
11
        "      <p>\n"
944
11
        "        <input type=\"hidden\" name=\"" OIDC_PROTO_RESPONSE_MODE
945
11
        "\" value=\"" OIDC_PROTO_RESPONSE_MODE_FRAGMENT "\">\n"
946
11
        "      </p>\n"
947
11
        "    </form>\n";
948
949
  /* prepare HTML/Javascript page to be sent in the content handler */
950
11
  return oidc_util_html_content_prep(r, OIDC_REQUEST_STATE_KEY_HTML, "Submitting...", java_script, "postOnLoad()",
951
11
             html_body);
952
11
}
953
954
/*
955
 * handle an authorization response from the OP using the Basic Client profile or a Hybrid flow
956
 */
957
6
static int oidc_redirect_uri_handle_response_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
958
6
  return oidc_response_authorization_redirect(r, c, session);
959
6
}
960
961
/*
962
 * handle an authorization response using the fragment(+POST) response_mode with the Implicit Client profile
963
 */
964
2.19k
static apr_byte_t oidc_redirect_uri_match_response_post(request_rec *r, oidc_cfg_t *c) {
965
2.19k
  return oidc_proto_response_is_post(r, c);
966
2.19k
}
967
968
/*
969
 * handle a response from the OP discovery page
970
 */
971
1.01k
static int oidc_redirect_uri_handle_discovery_response(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
972
1.01k
  return oidc_discovery_response(r, c);
973
1.01k
}
974
975
/*
976
 * pass the request on to the content handler; avoid:
977
 * "No authentication done but request not allowed without authentication"
978
 * by setting r->user
979
 */
980
117
static int oidc_redirect_uri_handle_in_content_handler(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
981
  /* no authentication happened, so any OIDC_* headers on this request are the
982
   * client's own and must not be passed on */
983
117
  oidc_scrub_headers(r);
984
117
  r->user = "";
985
117
  return OK;
986
117
}
987
988
/*
989
 * handle a request object by reference request
990
 */
991
7
static int oidc_redirect_uri_handle_request_uri(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
992
7
  return oidc_request_uri(r, c);
993
7
}
994
995
/*
996
 * handle a request to invalidate the access token cache
997
 */
998
2
static int oidc_redirect_uri_handle_remove_at_cache(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
999
2
  return oidc_revoke_at_cache_remove(r, c);
1000
2
}
1001
1002
/*
1003
 * handle a request to revoke a user session
1004
 */
1005
12
static int oidc_redirect_uri_handle_revoke_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1006
12
  return oidc_revoke_session(r, c);
1007
12
}
1008
1009
/*
1010
 * handle a request to the info hook
1011
 */
1012
51
static int oidc_redirect_uri_handle_info(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1013
51
  apr_byte_t needs_save = FALSE;
1014
51
  char *s_extend_session = NULL;
1015
51
  int rc = OK;
1016
1017
51
  oidc_util_url_parameter_get(r, OIDC_INFO_PARAM_EXTEND_SESSION, &s_extend_session);
1018
1019
  // need to establish user/claims for authorization purposes
1020
51
  rc = oidc_handle_existing_session(
1021
51
      r, c, session, (s_extend_session == NULL) || (_oidc_strcmp(s_extend_session, "false") != 0), &needs_save);
1022
1023
  // retain this session across the authentication and content handler phases
1024
  // by storing it in the request state
1025
51
  apr_pool_userdata_set(session, OIDC_USERDATA_SESSION, NULL, r->pool);
1026
1027
  // record whether the session was modified and needs to be saved in the cache
1028
51
  if (needs_save)
1029
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SAVE, "");
1030
1031
51
  return rc;
1032
51
}
1033
1034
/*
1035
 * match a "bare" request to the redirect URI, indicating implicit flow using the fragment response_mode
1036
 */
1037
15
static apr_byte_t oidc_redirect_uri_match_bare(request_rec *r, oidc_cfg_t *c) {
1038
15
  return (r->args == NULL) || (_oidc_strcmp(r->args, "") == 0);
1039
15
}
1040
1041
11
static int oidc_redirect_uri_handle_implicit(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1042
11
  return oidc_javascript_implicit(r, c);
1043
11
}
1044
1045
/* dispatch-table entry for one redirect_uri sub-feature */
1046
typedef struct oidc_redirect_uri_dispatch_t {
1047
  /* matches the request to this sub-feature; when NULL the request matches on the query parameter below */
1048
  apr_byte_t (*match)(request_rec *r, oidc_cfg_t *c);
1049
  /* query parameter that selects this sub-feature when match is NULL */
1050
  const char *parameter;
1051
  /* require an authenticated session, returning HTTP_UNAUTHORIZED (before counting) when there is none */
1052
  apr_byte_t requires_auth;
1053
  /* handles the matched request */
1054
  int (*handle)(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);
1055
  /* metrics counter identifying this sub-feature */
1056
  oidc_metrics_counter_type_t metric;
1057
  /* add the authentication-response timing metric after handling */
1058
  apr_byte_t timing;
1059
} oidc_redirect_uri_dispatch_t;
1060
1061
/*
1062
 * Ordered redirect_uri dispatch. Logout precedes POST authorization responses so back-channel
1063
 * logout reaches its handler.
1064
 */
1065
// clang-format off
1066
static const oidc_redirect_uri_dispatch_t _oidc_redirect_uri_dispatch[] = {
1067
    {oidc_proto_response_is_redirect, NULL, FALSE, oidc_redirect_uri_handle_response_redirect, OM_REDIRECT_URI_AUTHN_RESPONSE_REDIRECT, TRUE},
1068
    {NULL, OIDC_REDIRECT_URI_REQUEST_LOGOUT, FALSE, oidc_logout, OM_REDIRECT_URI_REQUEST_LOGOUT, FALSE},
1069
    {oidc_redirect_uri_match_response_post, NULL, FALSE, oidc_response_authorization_post, OM_REDIRECT_URI_AUTHN_RESPONSE_POST, TRUE},
1070
    {oidc_is_discovery_response, NULL, FALSE, oidc_redirect_uri_handle_discovery_response, OM_REDIRECT_URI_DISCOVERY_RESPONSE, FALSE},
1071
    {NULL, OIDC_REDIRECT_URI_REQUEST_JWKS, FALSE, oidc_redirect_uri_handle_in_content_handler, OM_REDIRECT_URI_REQUEST_JWKS, FALSE},
1072
    {NULL, OIDC_REDIRECT_URI_REQUEST_SESSION, FALSE, oidc_session_management, OM_REDIRECT_URI_REQUEST_SESSION, FALSE},
1073
    {NULL, OIDC_REDIRECT_URI_REQUEST_REFRESH, FALSE, oidc_refresh_token_request, OM_REDIRECT_URI_REQUEST_REFRESH, FALSE},
1074
    {NULL, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, FALSE, oidc_redirect_uri_handle_request_uri, OM_REDIRECT_URI_REQUEST_REQUEST_URI, FALSE},
1075
    {NULL, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, FALSE, oidc_redirect_uri_handle_remove_at_cache, OM_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, FALSE},
1076
    {NULL, OIDC_REDIRECT_URI_REQUEST_REVOKE_SESSION, FALSE, oidc_redirect_uri_handle_revoke_session, OM_REDIRECT_URI_REQUEST_REVOKE_SESSION, FALSE},
1077
    {NULL, OIDC_REDIRECT_URI_REQUEST_DPOP, FALSE, oidc_redirect_uri_handle_in_content_handler, OM_REDIRECT_URI_REQUEST_DPOP, FALSE},
1078
    {NULL, OIDC_REDIRECT_URI_REQUEST_INFO, TRUE, oidc_redirect_uri_handle_info, OM_REDIRECT_URI_REQUEST_INFO, FALSE},
1079
    {oidc_redirect_uri_match_bare, NULL, FALSE, oidc_redirect_uri_handle_implicit, OM_REDIRECT_URI_AUTHN_RESPONSE_IMPLICIT, FALSE},
1080
};
1081
// clang-format on
1082
1083
/*
1084
 * handle all requests to the redirect_uri
1085
 */
1086
4.69k
int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1087
1088
4.69k
  const oidc_redirect_uri_dispatch_t *entry = NULL;
1089
4.69k
  int rc = OK;
1090
1091
4.69k
  OIDC_METRICS_TIMING_START(r, c);
1092
1093
14.6k
  for (int i = 0; i < (int)(sizeof(_oidc_redirect_uri_dispatch) / sizeof(oidc_redirect_uri_dispatch_t)); i++) {
1094
1095
14.6k
    entry = &_oidc_redirect_uri_dispatch[i];
1096
1097
14.6k
    if (entry->match ? (entry->match(r, c) == FALSE)
1098
14.6k
         : (oidc_util_url_has_parameter(r, entry->parameter) == FALSE))
1099
9.99k
      continue;
1100
1101
4.69k
    if ((entry->requires_auth) && (session->remote_user == NULL))
1102
1
      return HTTP_UNAUTHORIZED;
1103
1104
4.69k
    OIDC_METRICS_COUNTER_INC(r, c, entry->metric);
1105
1106
4.69k
    rc = entry->handle(r, c, session);
1107
1108
4.69k
    if (entry->timing) {
1109
757
      OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_RESPONSE);
1110
757
    }
1111
1112
4.69k
    return rc;
1113
4.69k
  }
1114
1115
  /* this is not an authorization response or logout request */
1116
1117
  /* check for "error" response */
1118
4
  if (oidc_util_url_has_parameter(r, OIDC_PROTO_ERROR)) {
1119
1120
2
    OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_PROVIDER);
1121
1122
2
    rc = oidc_response_authorization_redirect(r, c, session);
1123
1124
2
    return rc;
1125
2
  }
1126
1127
2
  OIDC_METRICS_COUNTER_INC(r, c, OM_REDIRECT_URI_ERROR_INVALID);
1128
1129
2
  oidc_error(
1130
2
      r, "The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR",
1131
2
      r->args);
1132
1133
  /* something went wrong */
1134
2
  return oidc_util_html_send_error(
1135
2
      r, "Invalid Request", apr_psprintf(r->pool, "The OpenID Connect callback URL received an invalid request"),
1136
2
      HTTP_INTERNAL_SERVER_ERROR);
1137
4
}
1138
1139
/*
1140
 * on a sub-request, try to recycle the authenticated user from the main/prev request;
1141
 * returns TRUE if the user could be recycled and the caller should return OK
1142
 */
1143
/*
1144
 * recycle r->user from the main (or previous, on internal redirect) request into a sub-request;
1145
 * returns FALSE when there is no user to recycle
1146
 */
1147
0
apr_byte_t oidc_subrequest_recycle_user(request_rec *r) {
1148
0
  if (r->main != NULL)
1149
0
    r->user = r->main->user;
1150
0
  else if (r->prev != NULL)
1151
0
    r->user = r->prev->user;
1152
1153
0
  if (r->user == NULL)
1154
0
    return FALSE;
1155
1156
0
  oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user);
1157
1158
0
  return TRUE;
1159
0
}
1160
1161
/* Restore parsed token state from the previous request after an internal redirect. */
1162
0
static apr_byte_t oidc_copy_tokens_from_prev_request_state(request_rec *r) {
1163
0
  const oidc_json_t *id_token = NULL;
1164
0
  const oidc_json_t *claims = NULL;
1165
0
  const char *scope = NULL;
1166
1167
0
  if (r->prev == NULL)
1168
0
    return FALSE;
1169
1170
0
  id_token = oidc_request_state_json_get(r->prev, OIDC_REQUEST_STATE_KEY_IDTOKEN);
1171
0
  if (id_token == NULL)
1172
0
    return FALSE;
1173
1174
  /* the (shallow) copies take their own JSON references, so the restored state stays valid
1175
   * independent of the previous request's cleanup order */
1176
0
  oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token);
1177
1178
0
  claims = oidc_request_state_json_get(r->prev, OIDC_REQUEST_STATE_KEY_CLAIMS);
1179
0
  if (claims != NULL)
1180
0
    oidc_request_state_json_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims);
1181
1182
0
  scope = oidc_request_state_get(r->prev, OIDC_REQUEST_STATE_KEY_SCOPE);
1183
0
  if (scope != NULL)
1184
0
    oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SCOPE, apr_pstrdup(r->pool, scope));
1185
1186
0
  oidc_debug(r, "restored the token request state from the previous request on this internal redirect");
1187
1188
0
  return TRUE;
1189
0
}
1190
1191
0
static apr_byte_t oidc_check_userid_openidc_subreq(request_rec *r) {
1192
  /* this is a sub-request and we may have a session (headers will have been scrubbed and set already) */
1193
0
  if (oidc_subrequest_recycle_user(r) == FALSE)
1194
0
    return FALSE;
1195
1196
  /* apparently request state can get lost in sub-requests, so see if id_token/claims need to be restored,
1197
   * preferably from the previous request's parsed state, falling back to a full session load */
1198
0
  if ((oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN) == NULL) &&
1199
0
      (oidc_copy_tokens_from_prev_request_state(r) == FALSE)) {
1200
0
    oidc_session_t *session = NULL;
1201
0
    oidc_session_load(r, &session);
1202
0
    oidc_copy_tokens_to_request_state(r, session);
1203
0
    oidc_session_free(r, session);
1204
0
  }
1205
1206
0
  oidc_strip_cookies(r);
1207
0
  return TRUE;
1208
0
}
1209
1210
/*
1211
 * handle a request to the redirect URI: dispatch, optionally retain the session and free, then return rc
1212
 */
1213
0
static int oidc_check_userid_openidc_redirect_uri(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1214
0
  int rc = oidc_handle_redirect_uri_request(r, c, session);
1215
1216
  /* see if the session needs to be retained for the content handler phase */
1217
0
  oidc_session_t *retain = NULL;
1218
0
  apr_pool_userdata_get((void **)&retain, OIDC_USERDATA_SESSION, r->pool);
1219
0
  if (retain == NULL)
1220
0
    oidc_session_free(r, session);
1221
1222
0
  return rc;
1223
0
}
1224
1225
/*
1226
 * handle an existing authenticated session: validate, persist if updated, free and strip cookies
1227
 */
1228
0
static int oidc_check_userid_openidc_existing_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
1229
0
  apr_byte_t needs_save = FALSE;
1230
0
  int rc = oidc_handle_existing_session(r, c, session, TRUE, &needs_save);
1231
0
  if ((rc == OK) && needs_save && (oidc_session_save(r, session, OIDC_SESSION_SAVE_UPDATE) == FALSE)) {
1232
0
    oidc_warn(r, "error saving session");
1233
0
    rc = HTTP_INTERNAL_SERVER_ERROR;
1234
0
  }
1235
1236
0
  oidc_session_free(r, session);
1237
0
  oidc_strip_cookies(r);
1238
1239
0
  return rc;
1240
0
}
1241
1242
/*
1243
 * main routine: handle OpenID Connect authentication
1244
 */
1245
0
static int oidc_check_userid_openidc(request_rec *r, oidc_cfg_t *c) {
1246
1247
0
  OIDC_METRICS_TIMING_START(r, c);
1248
1249
0
  if (oidc_util_url_redirect_uri(r, c) == NULL) {
1250
0
    oidc_error(r, "configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT
1251
0
            "\" but " OIDCRedirectURI " has not been set");
1252
0
    return HTTP_INTERNAL_SERVER_ERROR;
1253
0
  }
1254
1255
  /* on a sub-request, try to recycle the user from the main/prev request; fall through if it cannot */
1256
0
  if (!ap_is_initial_req(r) && (oidc_check_userid_openidc_subreq(r) == TRUE))
1257
0
    return OK;
1258
1259
  /* load the session from the request state; this will be a new "empty" session if no state exists */
1260
0
  oidc_session_t *session = NULL;
1261
0
  oidc_session_load(r, &session);
1262
1263
  /* see if the initial request is to the redirect URI; this handles potential logout too */
1264
0
  if (oidc_util_url_matches_redirect_uri(r, c) == TRUE)
1265
0
    return oidc_check_userid_openidc_redirect_uri(r, c, session);
1266
1267
  /* initial request to non-redirect URI with an existing session */
1268
0
  if (session->remote_user != NULL) {
1269
0
    int rc = oidc_check_userid_openidc_existing_session(r, c, session);
1270
0
    if (rc == OK) {
1271
0
      OIDC_METRICS_TIMING_ADD(r, c, OM_SESSION_VALID);
1272
0
    } else {
1273
0
      OIDC_METRICS_COUNTER_INC(r, c, OM_SESSION_ERROR_GENERAL);
1274
0
    }
1275
0
    return rc;
1276
0
  }
1277
1278
0
  oidc_session_free(r, session);
1279
1280
  /* no session and not an authorization or discovery response: default flow for unauthenticated users */
1281
0
  return oidc_handle_unauthenticated_user(r, c);
1282
0
}
1283
1284
/*
1285
 * main routine: handle "mixed" OIDC/OAuth authentication
1286
 */
1287
0
static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg_t *c) {
1288
1289
  /* get the bearer access token from the Authorization header */
1290
0
  const char *access_token = NULL;
1291
0
  if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE) {
1292
1293
0
    r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_OAUTH20);
1294
0
    return oidc_oauth_check_userid(r, c, access_token);
1295
0
  }
1296
1297
0
  if (r->method_number == M_OPTIONS) {
1298
    /* see the identical case in oidc_oauth_check_userid(): a CORS preflight is let
1299
     * through unauthenticated, so its OIDC_* headers are the client's own */
1300
0
    oidc_scrub_headers(r);
1301
0
    r->user = "";
1302
0
    return OK;
1303
0
  }
1304
1305
  /* no bearer token found: then treat this as a regular OIDC browser request */
1306
0
  r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_CONNECT);
1307
0
  return oidc_check_userid_openidc(r, c);
1308
0
}
1309
1310
0
int oidc_fixups(request_rec *r) {
1311
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1312
0
  if (oidc_enabled(r, c) == TRUE) {
1313
0
    OIDC_METRICS_TIMING_REQUEST_ADD(r, c, OM_MOD_AUTH_OPENIDC);
1314
0
    return OK;
1315
0
  }
1316
0
  return DECLINED;
1317
0
}
1318
1319
/*
1320
 * generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines
1321
 */
1322
0
int oidc_check_user_id(request_rec *r) {
1323
1324
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1325
0
  int rv = DECLINED;
1326
1327
0
  OIDC_METRICS_TIMING_REQUEST_START(r, c);
1328
1329
  /* log some stuff about the incoming HTTP request */
1330
0
  oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d", r->parsed_uri.path, r->args,
1331
0
       ap_is_initial_req(r));
1332
1333
0
  if (oidc_enabled(r, c) == FALSE) {
1334
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_DECLINED);
1335
0
    return DECLINED;
1336
0
  }
1337
1338
0
  oidc_util_set_trace_parent(r, c, NULL);
1339
1340
0
  OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_MOD_AUTH_OPENIDC);
1341
1342
  /* see if we've configured OpenID Connect user authentication for this request */
1343
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) {
1344
1345
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OPENID_CONNECT);
1346
0
    r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r));
1347
0
    rv = oidc_check_userid_openidc(r, c);
1348
1349
    /* see if we've configured OAuth 2.0 access control for this request */
1350
0
  } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
1351
1352
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_OAUTH20);
1353
0
    r->ap_auth_type = apr_pstrdup(r->pool, ap_auth_type(r));
1354
0
    rv = oidc_oauth_check_userid(r, c, NULL);
1355
1356
    /* see if we've configured "mixed mode" for this request */
1357
0
  } else if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0) {
1358
1359
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHTYPE_AUTH_OPENIDC);
1360
0
    rv = oidc_check_mixed_userid_oauth(r, c);
1361
0
  }
1362
1363
0
  return rv;
1364
0
}
1365
1366
/*
1367
 * check of mod_auth_openidc needs to handle this request
1368
 */
1369
158
apr_byte_t oidc_enabled(request_rec *r, oidc_cfg_t *c) {
1370
1371
158
  if (ap_auth_type(r) == NULL)
1372
0
    return FALSE;
1373
1374
158
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0)
1375
158
    return TRUE;
1376
1377
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0)
1378
0
    return TRUE;
1379
1380
0
  if (_oidc_strnatcasecmp(ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0)
1381
0
    return TRUE;
1382
1383
0
  return FALSE;
1384
0
}
1385
1386
/*
1387
 * SSL initialization magic copied from mod_auth_cas
1388
 */
1389
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1390
1391
static apr_thread_mutex_t **ssl_locks;
1392
static int ssl_num_locks;
1393
1394
static void oidc_ssl_locking_callback(int mode, int type, const char *file, int line) {
1395
  if (type < ssl_num_locks) {
1396
    if (mode & CRYPTO_LOCK)
1397
      apr_thread_mutex_lock(ssl_locks[type]);
1398
    else
1399
      apr_thread_mutex_unlock(ssl_locks[type]);
1400
  }
1401
}
1402
1403
#ifdef OPENSSL_NO_THREADID
1404
static unsigned long oidc_ssl_id_callback(void) {
1405
  return (unsigned long)apr_os_thread_current();
1406
}
1407
#else
1408
static void oidc_ssl_id_callback(CRYPTO_THREADID *id) {
1409
  CRYPTO_THREADID_set_numeric(id, (unsigned long)apr_os_thread_current());
1410
}
1411
#endif /* OPENSSL_NO_THREADID */
1412
1413
#endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */
1414
1415
/*
1416
 * cleanup resources allocated in a process
1417
 */
1418
0
static apr_status_t oidc_process_cleanup(void *data) {
1419
1420
0
  server_rec *sp = (server_rec *)data;
1421
0
  while (sp != NULL) {
1422
0
    oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1423
0
    oidc_cfg_process_cleanup(cfg, sp);
1424
0
    sp = sp->next;
1425
0
  }
1426
1427
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1428
  if (CRYPTO_get_locking_callback() == oidc_ssl_locking_callback)
1429
    CRYPTO_set_locking_callback(NULL);
1430
#ifdef OPENSSL_NO_THREADID
1431
  if (CRYPTO_get_id_callback() == oidc_ssl_id_callback)
1432
    CRYPTO_set_id_callback(NULL);
1433
#else
1434
  if (CRYPTO_THREADID_get_callback() == oidc_ssl_id_callback)
1435
    CRYPTO_THREADID_set_callback(NULL);
1436
#endif /* OPENSSL_NO_THREADID */
1437
1438
#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */
1439
1440
0
  EVP_cleanup();
1441
0
  oidc_http_cleanup();
1442
1443
0
  ap_log_error(APLOG_MARK, APLOG_INFO, 0, (server_rec *)data, "%s - shutdown", NAMEVERSION);
1444
1445
0
  return APR_SUCCESS;
1446
0
}
1447
1448
/*
1449
 * handler that is called (twice) after the configuration phase; check if everything is OK
1450
 */
1451
0
static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s) {
1452
0
  const char *userdata_key = "oidc_post_config";
1453
0
  void *data = NULL;
1454
1455
  /* Since the post_config hook is invoked twice (once
1456
   * for 'sanity checking' of the config and once for
1457
   * the actual server launch, we have to use a hack
1458
   * to not run twice
1459
   */
1460
0
  apr_pool_userdata_get(&data, userdata_key, s->process->pool);
1461
0
  if (data == NULL) {
1462
0
    apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool);
1463
0
    return OK;
1464
0
  }
1465
1466
0
#ifdef USE_MEMCACHE
1467
0
#define _OIDC_USE_MEMCACHE "yes"
1468
#else
1469
#define _OIDC_USE_MEMCACHE "no"
1470
#endif
1471
1472
#ifdef USE_LIBHIREDIS
1473
#define _OIDC_USE_REDIS "yes"
1474
#else
1475
0
#define _OIDC_USE_REDIS "no"
1476
0
#endif
1477
1478
#ifdef USE_LIBJQ
1479
#define _OIDC_USE_JQ "yes"
1480
#else
1481
0
#define _OIDC_USE_JQ "no"
1482
0
#endif
1483
1484
0
  ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
1485
0
         "%s - init - cjose %s, %s, EC=%s, GCM=%s, Memcache=%s, Redis=%s, JQ=%s", NAMEVERSION,
1486
0
         oidc_jose_version(), oidc_util_openssl_version(s->process->pool),
1487
0
         OIDC_JOSE_EC_SUPPORT ? "yes" : "no", OIDC_JOSE_GCM_SUPPORT ? "yes" : "no", _OIDC_USE_MEMCACHE,
1488
0
         _OIDC_USE_REDIS, _OIDC_USE_JQ);
1489
1490
0
  oidc_http_init();
1491
1492
#if ((OPENSSL_VERSION_NUMBER < 0x10100000) && defined(OPENSSL_THREADS) && APR_HAS_THREADS)
1493
  ssl_num_locks = CRYPTO_num_locks();
1494
  ssl_locks = apr_pcalloc(s->process->pool, ssl_num_locks * sizeof(*ssl_locks));
1495
1496
  int i;
1497
  for (i = 0; i < ssl_num_locks; i++)
1498
    apr_thread_mutex_create(&(ssl_locks[i]), APR_THREAD_MUTEX_DEFAULT, s->process->pool);
1499
1500
#ifdef OPENSSL_NO_THREADID
1501
  if (CRYPTO_get_locking_callback() == NULL && CRYPTO_get_id_callback() == NULL) {
1502
    CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
1503
    CRYPTO_set_id_callback(oidc_ssl_id_callback);
1504
  }
1505
#else
1506
  if (CRYPTO_get_locking_callback() == NULL && CRYPTO_THREADID_get_callback() == NULL) {
1507
    CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
1508
    CRYPTO_THREADID_set_callback(oidc_ssl_id_callback);
1509
  }
1510
#endif /* OPENSSL_NO_THREADID */
1511
1512
#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000) && defined (OPENSSL_THREADS) && APR_HAS_THREADS */
1513
1514
0
  apr_pool_cleanup_register(pool, s, oidc_process_cleanup, apr_pool_cleanup_null);
1515
1516
  /* must come after the oidc_process_cleanup registration: pool cleanups run last-in-first-out
1517
   * and the pooled easy handles have to be cleaned up before that cleanup's
1518
   * curl_global_cleanup() tears down libcurl underneath them */
1519
0
  oidc_http_curl_pool_init(pool);
1520
1521
0
  if (oidc_cfg_dir_post_config(s) != OK)
1522
0
    return HTTP_INTERNAL_SERVER_ERROR;
1523
1524
0
  server_rec *sp = s;
1525
0
  while (sp != NULL) {
1526
0
    oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1527
0
    if (oidc_cfg_post_config(pool, cfg, sp) != OK)
1528
0
      return HTTP_INTERNAL_SERVER_ERROR;
1529
0
    sp = sp->next;
1530
0
  }
1531
1532
0
  return oidc_cfg_check_vhosts(pool, s);
1533
0
}
1534
1535
/*
1536
 * parse an Apache expression in the configured require value
1537
 */
1538
0
static const char *oidc_parse_config(cmd_parms *cmd, const char *require_line, const void **parsed_require_line) {
1539
0
  const char *expr_err = NULL;
1540
0
  const ap_expr_info_t *expr;
1541
1542
0
  expr = ap_expr_parse_cmd(cmd, require_line, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL);
1543
1544
0
  if (expr_err)
1545
0
    return apr_pstrcat(cmd->temp_pool, "Cannot parse expression in require line: ", expr_err, NULL);
1546
1547
0
  *parsed_require_line = expr;
1548
1549
0
  return NULL;
1550
0
}
1551
1552
static const authz_provider oidc_authz_claim_provider = {
1553
    &oidc_authz_24_checker_claim,
1554
    &oidc_parse_config,
1555
};
1556
#ifdef USE_LIBJQ
1557
static const authz_provider oidc_authz_claims_expr_provider = {
1558
    &oidc_authz_24_checker_claims_expr,
1559
    NULL,
1560
};
1561
#endif
1562
1563
/*
1564
 * initialize cache context in child process if required
1565
 */
1566
0
static void oidc_child_init(apr_pool_t *p, server_rec *s) {
1567
0
  server_rec *sp = s;
1568
  /* drop any curl handles inherited over fork(): their connections share descriptors and
1569
   * TLS state with the parent process */
1570
0
  oidc_http_curl_pool_child_init();
1571
0
  while (sp != NULL) {
1572
0
    const oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(sp->module_config, &auth_openidc_module);
1573
0
    oidc_cfg_child_init(p, cfg, sp);
1574
0
    sp = sp->next;
1575
0
  }
1576
0
}
1577
1578
static const char oidcFilterName[] = "oidc_filter_in_filter";
1579
1580
/*
1581
 * add filter for inserting POST data
1582
 */
1583
0
static void oidc_filter_in_insert_filter(request_rec *r) {
1584
1585
0
  oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1586
1587
0
  if (oidc_enabled(r, c) == FALSE)
1588
0
    return;
1589
1590
0
  if (ap_is_initial_req(r) == 0)
1591
0
    return;
1592
1593
0
  apr_table_t *userdata_post_params = NULL;
1594
0
  apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, r->pool);
1595
0
  if (userdata_post_params == NULL)
1596
0
    return;
1597
1598
0
  ap_add_input_filter(oidcFilterName, NULL, r, r->connection);
1599
0
}
1600
1601
typedef struct oidc_filter_in_context {
1602
  apr_bucket_brigade *pbbTmp;
1603
  apr_size_t nbytes;
1604
} oidc_filter_in_context;
1605
1606
/*
1607
 * append a bucket with the captured POST parameters as form-encoded data to the brigade and
1608
 * update the Content-Length request header accordingly; no-op if there are no captured parameters
1609
 */
1610
static void oidc_filter_in_filter_append_post_params(ap_filter_t *f, apr_bucket_brigade *brigade,
1611
0
                 oidc_filter_in_context *ctx) {
1612
1613
0
  apr_table_t *userdata_post_params = NULL;
1614
0
  apr_pool_userdata_get((void **)&userdata_post_params, OIDC_USERDATA_POST_PARAMS_KEY, f->r->pool);
1615
0
  if (userdata_post_params == NULL)
1616
0
    return;
1617
1618
0
  const char *buf = apr_psprintf(f->r->pool, "%s%s", ctx->nbytes > 0 ? "&" : "",
1619
0
               oidc_http_form_encoded_data(f->r, userdata_post_params));
1620
0
  apr_bucket *b_out = apr_bucket_heap_create(buf, _oidc_strlen(buf), 0, f->r->connection->bucket_alloc);
1621
1622
0
  APR_BRIGADE_INSERT_TAIL(brigade, b_out);
1623
1624
0
  ctx->nbytes += _oidc_strlen(buf);
1625
1626
0
  if (oidc_http_hdr_in_content_length_get(f->r) != NULL)
1627
0
    oidc_http_hdr_in_set(f->r, OIDC_HTTP_HDR_CONTENT_LENGTH,
1628
0
             apr_psprintf(f->r->pool, "%ld", (long)ctx->nbytes));
1629
1630
0
  apr_pool_userdata_set(NULL, OIDC_USERDATA_POST_PARAMS_KEY, NULL, f->r->pool);
1631
0
}
1632
1633
/*
1634
 * execute filter for inserting POST data
1635
 */
1636
static apr_status_t oidc_filter_in_filter(ap_filter_t *f, apr_bucket_brigade *brigade, ap_input_mode_t mode,
1637
0
            apr_read_type_e block, apr_off_t nbytes) {
1638
0
  oidc_filter_in_context *ctx = NULL;
1639
0
  apr_bucket *b_in = NULL;
1640
0
  apr_status_t rc = APR_SUCCESS;
1641
1642
0
  if (!(ctx = f->ctx)) {
1643
0
    ctx = apr_palloc(f->r->pool, sizeof *ctx);
1644
0
    f->ctx = ctx;
1645
0
    ctx->pbbTmp = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
1646
0
    ctx->nbytes = 0;
1647
0
  }
1648
1649
0
  if (APR_BRIGADE_EMPTY(ctx->pbbTmp)) {
1650
0
    rc = ap_get_brigade(f->next, ctx->pbbTmp, mode, block, nbytes);
1651
1652
0
    if (mode == AP_MODE_EATCRLF || rc != APR_SUCCESS)
1653
0
      return rc;
1654
0
  }
1655
1656
0
  while (!APR_BRIGADE_EMPTY(ctx->pbbTmp)) {
1657
1658
0
    b_in = APR_BRIGADE_FIRST(ctx->pbbTmp);
1659
0
    APR_BUCKET_REMOVE(b_in);
1660
1661
0
    if (APR_BUCKET_IS_EOS(b_in)) {
1662
0
      oidc_filter_in_filter_append_post_params(f, brigade, ctx);
1663
0
      APR_BRIGADE_INSERT_TAIL(brigade, b_in);
1664
0
      break;
1665
0
    }
1666
1667
0
    APR_BRIGADE_INSERT_TAIL(brigade, b_in);
1668
0
    ctx->nbytes += b_in->length;
1669
0
  }
1670
1671
0
  return rc;
1672
0
}
1673
1674
/*
1675
 * register our authentication and authorization functions
1676
 */
1677
0
static void oidc_register_hooks(apr_pool_t *pool) {
1678
0
  oidc_pre_config_init();
1679
0
  ap_hook_post_config(oidc_post_config, NULL, NULL, APR_HOOK_LAST);
1680
0
  ap_hook_child_init(oidc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1681
0
  ap_hook_fixups(oidc_fixups, NULL, NULL, APR_HOOK_MIDDLE);
1682
0
  static const char *const proxySucc[] = {"mod_proxy.c", NULL};
1683
0
  ap_hook_handler(oidc_content_handler, NULL, proxySucc, APR_HOOK_FIRST);
1684
0
  ap_hook_insert_filter(oidc_filter_in_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
1685
0
  ap_register_input_filter(oidcFilterName, oidc_filter_in_filter, NULL, AP_FTYPE_RESOURCE);
1686
0
  ap_hook_check_authn(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF);
1687
0
  ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider,
1688
0
          AP_AUTH_INTERNAL_PER_CONF);
1689
#ifdef USE_LIBJQ
1690
  ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0",
1691
          &oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF);
1692
#endif
1693
0
}
1694
1695
// clang-format off
1696
module AP_MODULE_DECLARE_DATA auth_openidc_module = {
1697
    STANDARD20_MODULE_STUFF,
1698
  oidc_cfg_dir_config_create,
1699
  oidc_cfg_dir_config_merge,
1700
  oidc_cfg_server_create,
1701
  oidc_cfg_server_merge,
1702
  oidc_cfg_cmds,
1703
  oidc_register_hooks
1704
};
1705
// clang-format on