/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 |