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