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-2021 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  * shared memory caching: mod_auth_mellon
46  *
47  * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
48  *
49  **************************************************************************/
50 
51 #include "apr_hash.h"
52 #include "apr_strings.h"
53 #include "ap_config.h"
54 #include "ap_provider.h"
55 #include "apr_lib.h"
56 #include "apr_file_io.h"
57 #include "apr_sha1.h"
58 #include "apr_base64.h"
59 
60 #include "httpd.h"
61 #include "http_core.h"
62 #include "http_config.h"
63 #include "http_log.h"
64 #include "http_protocol.h"
65 #include "http_request.h"
66 
67 #include "mod_auth_openidc.h"
68 
69 #define OIDC_REFRESH_ERROR 2
70 
71 static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
72 		oidc_session_t *session, const char *url);
73 
74 // TODO:
75 // - sort out oidc_cfg vs. oidc_dir_cfg stuff
76 // - rigid input checking on discovery responses
77 // - check self-issued support
78 // - README.quickstart
79 // - refresh metadata once-per too? (for non-signing key changes)
80 
81 extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
82 
83 /*
84  * clean any suspicious headers in the HTTP request sent by the user agent
85  */
oidc_scrub_request_headers(request_rec * r,const char * claim_prefix,apr_hash_t * scrub)86 static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix,
87 		apr_hash_t *scrub) {
88 
89 	const int prefix_len = claim_prefix ? strlen(claim_prefix) : 0;
90 
91 	/* get an array representation of the incoming HTTP headers */
92 	const apr_array_header_t *const h = apr_table_elts(r->headers_in);
93 
94 	/* table to keep the non-suspicious headers */
95 	apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts);
96 
97 	/* loop over the incoming HTTP headers */
98 	const apr_table_entry_t *const e = (const apr_table_entry_t*) h->elts;
99 	int i;
100 	for (i = 0; i < h->nelts; i++) {
101 		const char *const k = e[i].key;
102 
103 		/* is this header's name equivalent to a header that needs scrubbing? */
104 		const char *hdr =
105 				(k != NULL) && (scrub != NULL) ?
106 						apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL;
107 		const int header_matches = (hdr != NULL)
108 						&& (oidc_strnenvcmp(k, hdr, -1) == 0);
109 
110 		/*
111 		 * would this header be interpreted as a mod_auth_openidc attribute? Note
112 		 * that prefix_len will be zero if no attr_prefix is defined,
113 		 * so this will always be false. Also note that we do not
114 		 * scrub headers if the prefix is empty because every header
115 		 * would match.
116 		 */
117 		const int prefix_matches = (k != NULL) && prefix_len
118 				&& (oidc_strnenvcmp(k, claim_prefix, prefix_len) == 0);
119 
120 		/* add to the clean_headers if non-suspicious, skip and report otherwise */
121 		if (!prefix_matches && !header_matches) {
122 			apr_table_addn(clean_headers, k, e[i].val);
123 		} else {
124 			oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k,
125 					e[i].val);
126 		}
127 	}
128 
129 	/* overwrite the incoming headers with the cleaned result */
130 	r->headers_in = clean_headers;
131 }
132 
133 /*
134  * scrub all mod_auth_openidc related headers
135  */
oidc_scrub_headers(request_rec * r)136 void oidc_scrub_headers(request_rec *r) {
137 	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
138 			&auth_openidc_module);
139 
140 	const char *prefix = oidc_cfg_claim_prefix(r);
141 	apr_hash_t *hdrs = apr_hash_make(r->pool);
142 
143 	if (apr_strnatcmp(prefix, "") == 0) {
144 		if ((cfg->white_listed_claims != NULL)
145 				&& (apr_hash_count(cfg->white_listed_claims) > 0))
146 			hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims, hdrs);
147 		else
148 			oidc_warn(r,
149 					"both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!");
150 	}
151 
152 	char *authn_hdr = oidc_cfg_dir_authn_header(r);
153 	if (authn_hdr != NULL)
154 		apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr);
155 
156 	/*
157 	 * scrub all headers starting with OIDC_ first
158 	 */
159 	oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs);
160 
161 	/*
162 	 * then see if the claim headers need to be removed on top of that
163 	 * (i.e. the prefix does not start with the default OIDC_)
164 	 */
165 	if ((strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) {
166 		oidc_scrub_request_headers(r, prefix, NULL);
167 	}
168 }
169 
170 /*
171  * strip the session cookie from the headers sent to the application/backend
172  */
oidc_strip_cookies(request_rec * r)173 void oidc_strip_cookies(request_rec *r) {
174 
175 	char *cookie, *ctx, *result = NULL;
176 	const char *name = NULL;
177 	int i;
178 
179 	apr_array_header_t *strip = oidc_dir_cfg_strip_cookies(r);
180 
181 	char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
182 
183 	if ((cookies != NULL) && (strip != NULL)) {
184 
185 		oidc_debug(r,
186 				"looking for the following cookies to strip from cookie header: %s",
187 				apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA));
188 
189 		cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx);
190 
191 		do {
192 			while (cookie != NULL && *cookie == OIDC_CHAR_SPACE)
193 				cookie++;
194 
195 			for (i = 0; i < strip->nelts; i++) {
196 				name = ((const char**) strip->elts)[i];
197 				if ((strncmp(cookie, name, strlen(name)) == 0)
198 						&& (cookie[strlen(name)] == OIDC_CHAR_EQUAL)) {
199 					oidc_debug(r, "stripping: %s", name);
200 					break;
201 				}
202 			}
203 
204 			if (i == strip->nelts) {
205 				result = result ? apr_psprintf(r->pool, "%s%s %s", result,
206 						OIDC_STR_SEMI_COLON, cookie) :
207 						cookie;
208 			}
209 
210 			cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx);
211 		} while (cookie != NULL);
212 
213 		oidc_util_hdr_in_cookie_set(r, result);
214 	}
215 }
216 
217 #define OIDC_SHA1_LEN 20
218 
219 /*
220  * calculates a hash value based on request fingerprint plus a provided nonce string.
221  */
oidc_get_browser_state_hash(request_rec * r,oidc_cfg * c,const char * nonce)222 static char* oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c,
223 		const char *nonce) {
224 
225 	oidc_debug(r, "enter");
226 
227 	/* helper to hold to header values */
228 	const char *value = NULL;
229 	/* the hash context */
230 	apr_sha1_ctx_t sha1;
231 
232 	/* Initialize the hash context */
233 	apr_sha1_init(&sha1);
234 
235 	if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR) {
236 		/* get the X-FORWARDED-FOR header value  */
237 		value = oidc_util_hdr_in_x_forwarded_for_get(r);
238 		/* if we have a value for this header, concat it to the hash input */
239 		if (value != NULL)
240 			apr_sha1_update(&sha1, value, strlen(value));
241 	}
242 
243 	if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_USER_AGENT) {
244 		/* get the USER-AGENT header value  */
245 		value = oidc_util_hdr_in_user_agent_get(r);
246 		/* if we have a value for this header, concat it to the hash input */
247 		if (value != NULL)
248 			apr_sha1_update(&sha1, value, strlen(value));
249 	}
250 
251 	/* get the remote client IP address or host name */
252 	/*
253 	 int remotehost_is_ip;
254 	 value = ap_get_remote_host(r->connection, r->per_dir_config,
255 	 REMOTE_NOLOOKUP, &remotehost_is_ip);
256 	 apr_sha1_update(&sha1, value, strlen(value));
257 	 */
258 
259 	/* concat the nonce parameter to the hash input */
260 	apr_sha1_update(&sha1, nonce, strlen(nonce));
261 
262 	/* concat the token binding ID if present */
263 	value = oidc_util_get_provided_token_binding_id(r);
264 	if (value != NULL) {
265 		oidc_debug(r,
266 				"Provided Token Binding ID environment variable found; adding its value to the state");
267 		apr_sha1_update(&sha1, value, strlen(value));
268 	}
269 
270 	/* finalize the hash input and calculate the resulting hash output */
271 	unsigned char hash[OIDC_SHA1_LEN];
272 	apr_sha1_final(hash, &sha1);
273 
274 	/* base64url-encode the resulting hash and return it */
275 	char *result = NULL;
276 	oidc_base64url_encode(r, &result, (const char*) hash, OIDC_SHA1_LEN, TRUE);
277 	return result;
278 }
279 
280 /*
281  * return the name for the state cookie
282  */
oidc_get_state_cookie_name(request_rec * r,const char * state)283 static char* oidc_get_state_cookie_name(request_rec *r, const char *state) {
284 	return apr_psprintf(r->pool, "%s%s", oidc_cfg_dir_state_cookie_prefix(r),
285 			state);
286 }
287 
288 /*
289  * return the static provider configuration, i.e. from a metadata URL or configuration primitives
290  */
oidc_provider_static_config(request_rec * r,oidc_cfg * c,oidc_provider_t ** provider)291 static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *c,
292 		oidc_provider_t **provider) {
293 
294 	json_t *j_provider = NULL;
295 	char *s_json = NULL;
296 
297 	/* see if we should configure a static provider based on external (cached) metadata */
298 	if ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) {
299 		*provider = &c->provider;
300 		return TRUE;
301 	}
302 
303 	oidc_cache_get_provider(r, c->provider.metadata_url, &s_json);
304 
305 	if (s_json == NULL) {
306 
307 		if (oidc_metadata_provider_retrieve(r, c, NULL,
308 				c->provider.metadata_url, &j_provider, &s_json) == FALSE) {
309 			oidc_error(r, "could not retrieve metadata from url: %s",
310 					c->provider.metadata_url);
311 			return FALSE;
312 		}
313 
314 		oidc_cache_set_provider(r, c->provider.metadata_url, s_json,
315 				apr_time_now() + (c->provider_metadata_refresh_interval <= 0 ? apr_time_from_sec( OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : c->provider_metadata_refresh_interval));
316 
317 	} else {
318 
319 		oidc_util_decode_json_object(r, s_json, &j_provider);
320 
321 		/* check to see if it is valid metadata */
322 		if (oidc_metadata_provider_is_valid(r, c, j_provider, NULL) == FALSE) {
323 			oidc_error(r,
324 					"cache corruption detected: invalid metadata from url: %s",
325 					c->provider.metadata_url);
326 			return FALSE;
327 		}
328 	}
329 
330 	*provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t));
331 	memcpy(*provider, &c->provider, sizeof(oidc_provider_t));
332 
333 	if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) {
334 		oidc_error(r, "could not parse metadata from url: %s",
335 				c->provider.metadata_url);
336 		if (j_provider)
337 			json_decref(j_provider);
338 		return FALSE;
339 	}
340 
341 	json_decref(j_provider);
342 
343 	return TRUE;
344 }
345 
346 /*
347  * return the oidc_provider_t struct for the specified issuer
348  */
oidc_get_provider_for_issuer(request_rec * r,oidc_cfg * c,const char * issuer,apr_byte_t allow_discovery)349 static oidc_provider_t* oidc_get_provider_for_issuer(request_rec *r,
350 		oidc_cfg *c, const char *issuer, apr_byte_t allow_discovery) {
351 
352 	/* by default we'll assume that we're dealing with a single statically configured OP */
353 	oidc_provider_t *provider = NULL;
354 	if (oidc_provider_static_config(r, c, &provider) == FALSE)
355 		return NULL;
356 
357 	/* unless a metadata directory was configured, so we'll try and get the provider settings from there */
358 	if (c->metadata_dir != NULL) {
359 
360 		/* try and get metadata from the metadata directory for the OP that sent this response */
361 		if ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery)
362 				== FALSE) || (provider == NULL)) {
363 
364 			/* don't know nothing about this OP/issuer */
365 			oidc_error(r, "no provider metadata found for issuer \"%s\"",
366 					issuer);
367 
368 			return NULL;
369 		}
370 	}
371 
372 	return provider;
373 }
374 
375 /*
376  * find out whether the request is a response from an IDP discovery page
377  */
oidc_is_discovery_response(request_rec * r,oidc_cfg * cfg)378 static apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg *cfg) {
379 	/*
380 	 * prereq: this is a call to the configured redirect_uri, now see if:
381 	 * the OIDC_DISC_OP_PARAM is present
382 	 */
383 	return oidc_util_request_has_parameter(r, OIDC_DISC_OP_PARAM)
384 			|| oidc_util_request_has_parameter(r, OIDC_DISC_USER_PARAM);
385 }
386 
387 /*
388  * return the HTTP method being called: only for POST data persistence purposes
389  */
oidc_original_request_method(request_rec * r,oidc_cfg * cfg,apr_byte_t handle_discovery_response)390 static const char* oidc_original_request_method(request_rec *r, oidc_cfg *cfg,
391 		apr_byte_t handle_discovery_response) {
392 	const char *method = OIDC_METHOD_GET;
393 
394 	char *m = NULL;
395 	if ((handle_discovery_response == TRUE)
396 			&& (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, cfg)))
397 			&& (oidc_is_discovery_response(r, cfg))) {
398 		oidc_util_get_request_parameter(r, OIDC_DISC_RM_PARAM, &m);
399 		if (m != NULL)
400 			method = apr_pstrdup(r->pool, m);
401 	} else {
402 
403 		/*
404 		 * if POST preserve is not enabled for this location, there's no point in preserving
405 		 * the method either which would result in POSTing empty data on return;
406 		 * so we revert to legacy behavior
407 		 */
408 		if (oidc_cfg_dir_preserve_post(r) == 0)
409 			return OIDC_METHOD_GET;
410 
411 		const char *content_type = oidc_util_hdr_in_content_type_get(r);
412 		if ((r->method_number == M_POST) && (apr_strnatcmp(content_type,
413 				OIDC_CONTENT_TYPE_FORM_ENCODED) == 0))
414 			method = OIDC_METHOD_FORM_POST;
415 	}
416 
417 	oidc_debug(r, "return: %s", method);
418 
419 	return method;
420 }
421 
422 /*
423  * send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage
424  */
oidc_post_preserve_javascript(request_rec * r,const char * location,char ** javascript,char ** javascript_method)425 apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location,
426 		char **javascript, char **javascript_method) {
427 
428 	if (oidc_cfg_dir_preserve_post(r) == 0)
429 		return FALSE;
430 
431 	oidc_debug(r, "enter");
432 
433 	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
434 			&auth_openidc_module);
435 
436 	const char *method = oidc_original_request_method(r, cfg, FALSE);
437 
438 	if (apr_strnatcmp(method, OIDC_METHOD_FORM_POST) != 0)
439 		return FALSE;
440 
441 	/* read the parameters that are POST-ed to us */
442 	apr_table_t *params = apr_table_make(r->pool, 8);
443 	if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) {
444 		oidc_error(r, "something went wrong when reading the POST parameters");
445 		return FALSE;
446 	}
447 
448 	const apr_array_header_t *arr = apr_table_elts(params);
449 	const apr_table_entry_t *elts = (const apr_table_entry_t*) arr->elts;
450 	int i;
451 	char *json = "";
452 	for (i = 0; i < arr->nelts; i++) {
453 		json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json,
454 				oidc_util_escape_string(r, elts[i].key),
455 				oidc_util_escape_string(r, elts[i].val),
456 				i < arr->nelts - 1 ? "," : "");
457 	}
458 	json = apr_psprintf(r->pool, "{ %s }", json);
459 
460 	const char *jmethod = "preserveOnLoad";
461 	const char *jscript =
462 			apr_psprintf(r->pool,
463 					"    <script type=\"text/javascript\">\n"
464 					"      function %s() {\n"
465 					"        sessionStorage.setItem('mod_auth_openidc_preserve_post_params', JSON.stringify(%s));\n"
466 					"        %s"
467 					"      }\n"
468 					"    </script>\n", jmethod, json,
469 					location ?
470 							apr_psprintf(r->pool, "window.location='%s';\n",
471 									location) :
472 									"");
473 	if (location == NULL) {
474 		if (javascript_method)
475 			*javascript_method = apr_pstrdup(r->pool, jmethod);
476 		if (javascript)
477 			*javascript = apr_pstrdup(r->pool, jscript);
478 	} else {
479 		oidc_util_html_send(r, "Preserving...", jscript, jmethod,
480 				"<p>Preserving...</p>", OK);
481 	}
482 
483 	return TRUE;
484 }
485 
486 /*
487  * restore POST parameters on original_url from HTML5 local storage
488  */
oidc_request_post_preserved_restore(request_rec * r,const char * original_url)489 static int oidc_request_post_preserved_restore(request_rec *r,
490 		const char *original_url) {
491 
492 	oidc_debug(r, "enter: original_url=%s", original_url);
493 
494 	const char *method = "postOnLoad";
495 	const char *script =
496 			apr_psprintf(r->pool,
497 					"    <script type=\"text/javascript\">\n"
498 					"      function str_decode(string) {\n"
499 					"        try {\n"
500 					"          result = decodeURIComponent(string);\n"
501 					"        } catch (e) {\n"
502 					"          result =  unescape(string);\n"
503 					"        }\n"
504 					"        return result;\n"
505 					"      }\n"
506 					"      function %s() {\n"
507 					"        var mod_auth_openidc_preserve_post_params = JSON.parse(sessionStorage.getItem('mod_auth_openidc_preserve_post_params'));\n"
508 					"		 sessionStorage.removeItem('mod_auth_openidc_preserve_post_params');\n"
509 					"        for (var key in mod_auth_openidc_preserve_post_params) {\n"
510 					"          var input = document.createElement(\"input\");\n"
511 					"          input.name = str_decode(key);\n"
512 					"          input.value = str_decode(mod_auth_openidc_preserve_post_params[key]);\n"
513 					"          input.type = \"hidden\";\n"
514 					"          document.forms[0].appendChild(input);\n"
515 					"        }\n"
516 					"        document.forms[0].action = '%s';\n"
517 					"        document.forms[0].submit();\n"
518 					"      }\n"
519 					"    </script>\n", method, original_url);
520 
521 	const char *body = "    <p>Restoring...</p>\n"
522 			"    <form method=\"post\"></form>\n";
523 
524 	return oidc_util_html_send(r, "Restoring...", script, method, body,
525 			OK);
526 }
527 
528 typedef struct oidc_state_cookies_t {
529 	char *name;
530 	apr_time_t timestamp;
531 	struct oidc_state_cookies_t *next;
532 } oidc_state_cookies_t;
533 
oidc_delete_oldest_state_cookies(request_rec * r,int number_of_valid_state_cookies,int max_number_of_state_cookies,oidc_state_cookies_t * first)534 static int oidc_delete_oldest_state_cookies(request_rec *r,
535 		int number_of_valid_state_cookies, int max_number_of_state_cookies,
536 		oidc_state_cookies_t *first) {
537 	oidc_state_cookies_t *cur = NULL, *prev = NULL, *prev_oldest = NULL,
538 			*oldest = NULL;
539 	while (number_of_valid_state_cookies >= max_number_of_state_cookies) {
540 		oldest = first;
541 		prev_oldest = NULL;
542 		prev = first;
543 		cur = first->next;
544 		while (cur) {
545 			if ((cur->timestamp < oldest->timestamp)) {
546 				oldest = cur;
547 				prev_oldest = prev;
548 			}
549 			prev = cur;
550 			cur = cur->next;
551 		}
552 		oidc_warn(r,
553 				"deleting oldest state cookie: %s (time until expiry %" APR_TIME_T_FMT " seconds)",
554 				oldest->name, apr_time_sec(oldest->timestamp - apr_time_now()));
555 		oidc_util_set_cookie(r, oldest->name, "", 0,
556 				OIDC_COOKIE_EXT_SAME_SITE_NONE(r));
557 		if (prev_oldest)
558 			prev_oldest->next = oldest->next;
559 		else
560 			first = first->next;
561 		number_of_valid_state_cookies--;
562 	}
563 	return number_of_valid_state_cookies;
564 }
565 
566 /*
567  * clean state cookies that have expired i.e. for outstanding requests that will never return
568  * successfully and return the number of remaining valid cookies/outstanding-requests while
569  * doing so
570  */
oidc_clean_expired_state_cookies(request_rec * r,oidc_cfg * c,const char * currentCookieName,int delete_oldest)571 static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
572 		const char *currentCookieName, int delete_oldest) {
573 	int number_of_valid_state_cookies = 0;
574 	oidc_state_cookies_t *first = NULL, *last = NULL;
575 	char *cookie, *tokenizerCtx = NULL;
576 	char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
577 	if (cookies != NULL) {
578 		cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
579 		while (cookie != NULL) {
580 			while (*cookie == OIDC_CHAR_SPACE)
581 				cookie++;
582 			if (strstr(cookie, oidc_cfg_dir_state_cookie_prefix(r)) == cookie) {
583 				char *cookieName = cookie;
584 				while (cookie != NULL && *cookie != OIDC_CHAR_EQUAL)
585 					cookie++;
586 				if (*cookie == OIDC_CHAR_EQUAL) {
587 					*cookie = '\0';
588 					cookie++;
589 					if ((currentCookieName == NULL)
590 							|| (apr_strnatcmp(cookieName, currentCookieName)
591 									!= 0)) {
592 						oidc_proto_state_t *proto_state =
593 								oidc_proto_state_from_cookie(r, c, cookie);
594 						if (proto_state != NULL) {
595 							json_int_t ts = oidc_proto_state_get_timestamp(
596 									proto_state);
597 							if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
598 								oidc_warn(r,
599 										"state (%s) has expired (original_url=%s)",
600 										cookieName,
601 										oidc_proto_state_get_original_url(
602 												proto_state));
603 								oidc_util_set_cookie(r, cookieName, "", 0,
604 										OIDC_COOKIE_EXT_SAME_SITE_NONE(r));
605 							} else {
606 								if (first == NULL) {
607 									first = apr_pcalloc(r->pool,
608 											sizeof(oidc_state_cookies_t));
609 									last = first;
610 								} else {
611 									last->next = apr_pcalloc(r->pool,
612 											sizeof(oidc_state_cookies_t));
613 									last = last->next;
614 								}
615 								last->name = cookieName;
616 								last->timestamp = ts;
617 								last->next = NULL;
618 								number_of_valid_state_cookies++;
619 							}
620 							oidc_proto_state_destroy(proto_state);
621 						} else {
622 							oidc_warn(r,
623 									"state cookie could not be retrieved/decoded, deleting: %s",
624 									cookieName);
625 							oidc_util_set_cookie(r, cookieName, "", 0,
626 									OIDC_COOKIE_EXT_SAME_SITE_NONE(r));
627 						}
628 					}
629 				}
630 			}
631 			cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
632 		}
633 	}
634 
635 	if (delete_oldest > 0)
636 		number_of_valid_state_cookies = oidc_delete_oldest_state_cookies(r,
637 				number_of_valid_state_cookies, c->max_number_of_state_cookies,
638 				first);
639 
640 	return number_of_valid_state_cookies;
641 }
642 
643 /*
644  * restore the state that was maintained between authorization request and response in an encrypted cookie
645  */
oidc_restore_proto_state(request_rec * r,oidc_cfg * c,const char * state,oidc_proto_state_t ** proto_state)646 static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
647 		const char *state, oidc_proto_state_t **proto_state) {
648 
649 	oidc_debug(r, "enter");
650 
651 	const char *cookieName = oidc_get_state_cookie_name(r, state);
652 
653 	/* clean expired state cookies to avoid pollution */
654 	oidc_clean_expired_state_cookies(r, c, cookieName, FALSE);
655 
656 	/* get the state cookie value first */
657 	char *cookieValue = oidc_util_get_cookie(r, cookieName);
658 	if (cookieValue == NULL) {
659 		oidc_error(r,
660 				"no \"%s\" state cookie found: check domain and samesite cookie settings",
661 				cookieName);
662 		return FALSE;
663 	}
664 
665 	/* clear state cookie because we don't need it anymore */
666 	oidc_util_set_cookie(r, cookieName, "", 0,
667 			OIDC_COOKIE_EXT_SAME_SITE_NONE(r));
668 
669 	*proto_state = oidc_proto_state_from_cookie(r, c, cookieValue);
670 	if (*proto_state == NULL)
671 		return FALSE;
672 
673 	const char *nonce = oidc_proto_state_get_nonce(*proto_state);
674 
675 	/* calculate the hash of the browser fingerprint concatenated with the nonce */
676 	char *calc = oidc_get_browser_state_hash(r, c, nonce);
677 	/* compare the calculated hash with the value provided in the authorization response */
678 	if (apr_strnatcmp(calc, state) != 0) {
679 		oidc_error(r,
680 				"calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"",
681 				state, calc);
682 		oidc_proto_state_destroy(*proto_state);
683 		return FALSE;
684 	}
685 
686 	apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state);
687 
688 	/* check that the timestamp is not beyond the valid interval */
689 	if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
690 		oidc_error(r, "state has expired");
691 		/*
692 		 * note that this overrides redirection to the OIDCDefaultURL as done later...
693 		 * see: https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/mod_auth_openidc/L4JFBw-XCNU/BWi2Fmk2AwAJ
694 		 */
695 		oidc_util_html_send_error(r, c->error_template,
696 				"Invalid Authentication Response",
697 				apr_psprintf(r->pool,
698 						"This is due to a timeout; please restart your authentication session by re-entering the URL/bookmark you originally wanted to access: %s",
699 						oidc_proto_state_get_original_url(*proto_state)),
700 						OK);
701 		oidc_proto_state_destroy(*proto_state);
702 		return FALSE;
703 	}
704 
705 	/* add the state */
706 	oidc_proto_state_set_state(*proto_state, state);
707 
708 	/* log the restored state object */
709 	oidc_debug(r, "restored state: %s",
710 			oidc_proto_state_to_string(r, *proto_state));
711 
712 	/* we've made it */
713 	return TRUE;
714 }
715 
716 /*
717  * set the state that is maintained between an authorization request and an authorization response
718  * in a cookie in the browser that is cryptographically bound to that state
719  */
oidc_authorization_request_set_cookie(request_rec * r,oidc_cfg * c,const char * state,oidc_proto_state_t * proto_state)720 static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
721 		const char *state, oidc_proto_state_t *proto_state) {
722 	/*
723 	 * create a cookie consisting of 8 elements:
724 	 * random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp
725 	 * encoded as JSON, encrypting the resulting JSON value
726 	 */
727 	char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state);
728 	if (cookieValue == NULL)
729 		return HTTP_INTERNAL_SERVER_ERROR;
730 
731 	/*
732 	 * clean expired state cookies to avoid pollution and optionally
733 	 * try to avoid the number of state cookies exceeding a max
734 	 */
735 	int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL,
736 			oidc_cfg_delete_oldest_state_cookies(c));
737 	int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
738 	if ((max_number_of_cookies > 0)
739 			&& (number_of_cookies >= max_number_of_cookies)) {
740 
741 		oidc_warn(r,
742 				"the number of existing, valid state cookies (%d) has exceeded the limit (%d), no additional authorization request + state cookie can be generated, aborting the request",
743 				number_of_cookies, max_number_of_cookies);
744 		/*
745 		 * TODO: the html_send code below caters for the case that there's a user behind a
746 		 * browser generating this request, rather than a piece of XHR code; how would an
747 		 * XHR client handle this?
748 		 */
749 
750 		/*
751 		 * it appears that sending content with a 503 turns the HTTP status code
752 		 * into a 200 so we'll avoid that for now: the user will see Apache specific
753 		 * readable text anyway
754 		 *
755 		 return oidc_util_html_send_error(r, c->error_template,
756 		 "Too Many Outstanding Requests",
757 		 apr_psprintf(r->pool,
758 		 "No authentication request could be generated since there are too many outstanding authentication requests already; you may have to wait up to %d seconds to be able to create a new request",
759 		 c->state_timeout),
760 		 HTTP_SERVICE_UNAVAILABLE);
761 		 */
762 
763 		return HTTP_SERVICE_UNAVAILABLE;
764 	}
765 
766 	/* assemble the cookie name for the state cookie */
767 	const char *cookieName = oidc_get_state_cookie_name(r, state);
768 
769 	/* set it as a cookie */
770 	oidc_util_set_cookie(r, cookieName, cookieValue, -1,
771 			OIDC_COOKIE_SAMESITE_LAX(c, r));
772 
773 	return OK;
774 }
775 
776 /*
777  * get the mod_auth_openidc related context from the (userdata in the) request
778  * (used for passing state between various Apache request processing stages and hook callbacks)
779  */
oidc_request_state(request_rec * rr)780 static apr_table_t* oidc_request_state(request_rec *rr) {
781 
782 	/* our state is always stored in the main request */
783 	request_rec *r = (rr->main != NULL) ? rr->main : rr;
784 
785 	/* our state is a table, get it */
786 	apr_table_t *state = NULL;
787 	apr_pool_userdata_get((void**) &state, OIDC_USERDATA_KEY, r->pool);
788 
789 	/* if it does not exist, we'll create a new table */
790 	if (state == NULL) {
791 		state = apr_table_make(r->pool, 5);
792 		apr_pool_userdata_set(state, OIDC_USERDATA_KEY, NULL, r->pool);
793 	}
794 
795 	/* return the resulting table, always non-null now */
796 	return state;
797 }
798 
799 /*
800  * set a name/value pair in the mod_auth_openidc-specific request context
801  * (used for passing state between various Apache request processing stages and hook callbacks)
802  */
oidc_request_state_set(request_rec * r,const char * key,const char * value)803 void oidc_request_state_set(request_rec *r, const char *key, const char *value) {
804 
805 	/* get a handle to the global state, which is a table */
806 	apr_table_t *state = oidc_request_state(r);
807 
808 	/* put the name/value pair in that table */
809 	apr_table_set(state, key, value);
810 }
811 
812 /*
813  * get a name/value pair from the mod_auth_openidc-specific request context
814  * (used for passing state between various Apache request processing stages and hook callbacks)
815  */
oidc_request_state_get(request_rec * r,const char * key)816 const char* oidc_request_state_get(request_rec *r, const char *key) {
817 
818 	/* get a handle to the global state, which is a table */
819 	apr_table_t *state = oidc_request_state(r);
820 
821 	/* return the value from the table */
822 	return apr_table_get(state, key);
823 }
824 
825 /*
826  * set the claims from a JSON object (c.q. id_token or user_info response) stored
827  * in the session in to HTTP headers passed on to the application
828  */
oidc_set_app_claims(request_rec * r,const oidc_cfg * const cfg,oidc_session_t * session,const char * s_claims)829 static apr_byte_t oidc_set_app_claims(request_rec *r, const oidc_cfg *const cfg,
830 		oidc_session_t *session, const char *s_claims) {
831 
832 	json_t *j_claims = NULL;
833 
834 	/* decode the string-encoded attributes in to a JSON structure */
835 	if (s_claims != NULL) {
836 		if (oidc_util_decode_json_object(r, s_claims, &j_claims) == FALSE)
837 			return FALSE;
838 	}
839 
840 	/* set the resolved claims a HTTP headers for the application */
841 	if (j_claims != NULL) {
842 		oidc_util_set_app_infos(r, j_claims, oidc_cfg_claim_prefix(r),
843 				cfg->claim_delimiter, oidc_cfg_dir_pass_info_in_headers(r),
844 				oidc_cfg_dir_pass_info_in_envvars(r),
845 				oidc_cfg_dir_pass_info_base64url(r));
846 
847 		/* release resources */
848 		json_decref(j_claims);
849 	}
850 
851 	return TRUE;
852 }
853 
854 static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
855 		oidc_provider_t *provider, const char *original_url,
856 		const char *login_hint, const char *id_token_hint, const char *prompt,
857 		const char *auth_request_params, const char *path_scope);
858 
859 /*
860  * log message about max session duration
861  */
oidc_log_session_expires(request_rec * r,const char * msg,apr_time_t session_expires)862 static void oidc_log_session_expires(request_rec *r, const char *msg,
863 		apr_time_t session_expires) {
864 	char buf[APR_RFC822_DATE_LEN + 1];
865 	apr_rfc822_date(buf, session_expires);
866 	oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf,
867 			apr_time_sec(session_expires - apr_time_now()));
868 }
869 
870 /*
871  * see if this is a non-browser request
872  */
oidc_is_xml_http_request(request_rec * r)873 static apr_byte_t oidc_is_xml_http_request(request_rec *r) {
874 
875 	if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
876 			&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
877 					OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
878 		return TRUE;
879 
880 	if ((oidc_util_hdr_in_accept_contains(r, OIDC_CONTENT_TYPE_TEXT_HTML)
881 			== FALSE) && (oidc_util_hdr_in_accept_contains(r,
882 					OIDC_CONTENT_TYPE_APP_XHTML_XML) == FALSE)
883 					&& (oidc_util_hdr_in_accept_contains(r,
884 							OIDC_CONTENT_TYPE_ANY) == FALSE))
885 		return TRUE;
886 
887 	return FALSE;
888 }
889 
890 /*
891  * find out which action we need to take when encountering an unauthenticated request
892  */
oidc_handle_unauthenticated_user(request_rec * r,oidc_cfg * c)893 static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) {
894 
895 	/* see if we've configured OIDCUnAuthAction for this path */
896 	switch (oidc_dir_cfg_unauth_action(r)) {
897 	case OIDC_UNAUTH_RETURN410:
898 		return HTTP_GONE;
899 	case OIDC_UNAUTH_RETURN407:
900 		return HTTP_PROXY_AUTHENTICATION_REQUIRED;
901 	case OIDC_UNAUTH_RETURN401:
902 		return HTTP_UNAUTHORIZED;
903 	case OIDC_UNAUTH_PASS:
904 		r->user = "";
905 
906 		/*
907 		 * we're not going to pass information about an authenticated user to the application,
908 		 * but we do need to scrub the headers that mod_auth_openidc would set for security reasons
909 		 */
910 		oidc_scrub_headers(r);
911 
912 		return OK;
913 
914 	case OIDC_UNAUTH_AUTHENTICATE:
915 
916 		/*
917 		 * exception handling: if this looks like a XMLHttpRequest call we
918 		 * won't redirect the user and thus avoid creating a state cookie
919 		 * for a non-browser (= Javascript) call that will never return from the OP
920 		 */
921 		if ((oidc_dir_cfg_unauth_expr_is_set(r) == FALSE)
922 				&& (oidc_is_xml_http_request(r) == TRUE))
923 			return HTTP_UNAUTHORIZED;
924 	}
925 
926 	/*
927 	 * else: no session (regardless of whether it is main or sub-request),
928 	 * and we need to authenticate the user
929 	 */
930 	return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
931 			NULL, NULL, oidc_dir_cfg_path_auth_request_params(r),
932 			oidc_dir_cfg_path_scope(r));
933 }
934 
935 /*
936  * check if maximum session duration was exceeded
937  */
oidc_check_max_session_duration(request_rec * r,oidc_cfg * cfg,oidc_session_t * session)938 static int oidc_check_max_session_duration(request_rec *r, oidc_cfg *cfg,
939 		oidc_session_t *session) {
940 
941 	/* get the session expiry from the session data */
942 	apr_time_t session_expires = oidc_session_get_session_expires(r, session);
943 
944 	/* check the expire timestamp against the current time */
945 	if (apr_time_now() > session_expires) {
946 		oidc_warn(r, "maximum session duration exceeded for user: %s",
947 				session->remote_user);
948 		oidc_session_kill(r, session);
949 		return oidc_handle_unauthenticated_user(r, cfg);
950 	}
951 
952 	/* log message about max session duration */
953 	oidc_log_session_expires(r, "session max lifetime", session_expires);
954 
955 	return OK;
956 }
957 
958 /*
959  * validate received session cookie against the domain it was issued for:
960  *
961  * this handles the case where the cache configured is a the same single memcache, Redis, or file
962  * backend for different (virtual) hosts, or a client-side cookie protected with the same secret
963  *
964  * it also handles the case that a cookie is unexpectedly shared across multiple hosts in
965  * name-based virtual hosting even though the OP(s) would be the same
966  */
oidc_check_cookie_domain(request_rec * r,oidc_cfg * cfg,oidc_session_t * session)967 static apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg *cfg,
968 		oidc_session_t *session) {
969 	const char *c_cookie_domain =
970 			cfg->cookie_domain ?
971 					cfg->cookie_domain : oidc_get_current_url_host(r);
972 	const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session);
973 	if ((s_cookie_domain == NULL)
974 			|| (apr_strnatcmp(c_cookie_domain, s_cookie_domain) != 0)) {
975 		oidc_warn(r,
976 				"aborting: detected attempt to play cookie against a different domain/host than issued for! (issued=%s, current=%s)",
977 				s_cookie_domain, c_cookie_domain);
978 		return FALSE;
979 	}
980 
981 	return TRUE;
982 }
983 
984 /*
985  * get a handle to the provider configuration via the "issuer" stored in the session
986  */
oidc_get_provider_from_session(request_rec * r,oidc_cfg * c,oidc_session_t * session,oidc_provider_t ** provider)987 apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg *c,
988 		oidc_session_t *session, oidc_provider_t **provider) {
989 
990 	oidc_debug(r, "enter");
991 
992 	/* get the issuer value from the session state */
993 	const char *issuer = oidc_session_get_issuer(r, session);
994 	if (issuer == NULL) {
995 		oidc_error(r, "session corrupted: no issuer found in session");
996 		return FALSE;
997 	}
998 
999 	/* get the provider info associated with the issuer value */
1000 	oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
1001 	if (p == NULL) {
1002 		oidc_error(r, "session corrupted: no provider found for issuer: %s",
1003 				issuer);
1004 		return FALSE;
1005 	}
1006 
1007 	*provider = p;
1008 
1009 	return TRUE;
1010 }
1011 
1012 /*
1013  * store claims resolved from the userinfo endpoint in the session
1014  */
oidc_store_userinfo_claims(request_rec * r,oidc_cfg * c,oidc_session_t * session,oidc_provider_t * provider,const char * claims,const char * userinfo_jwt)1015 static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *c,
1016 		oidc_session_t *session, oidc_provider_t *provider, const char *claims,
1017 		const char *userinfo_jwt) {
1018 
1019 	oidc_debug(r, "enter");
1020 
1021 	/* see if we've resolved any claims */
1022 	if (claims != NULL) {
1023 		/*
1024 		 * Successfully decoded a set claims from the response so we can store them
1025 		 * (well actually the stringified representation in the response)
1026 		 * in the session context safely now
1027 		 */
1028 		oidc_session_set_userinfo_claims(r, session, claims);
1029 
1030 		if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
1031 			/* this will also clear the entry if a JWT was not returned at this point */
1032 			oidc_session_set_userinfo_jwt(r, session, userinfo_jwt);
1033 		}
1034 
1035 	} else {
1036 		/*
1037 		 * clear the existing claims because we could not refresh them
1038 		 */
1039 		oidc_session_set_userinfo_claims(r, session, NULL);
1040 
1041 		oidc_session_set_userinfo_jwt(r, session, NULL);
1042 	}
1043 
1044 	/* store the last refresh time if we've configured a userinfo refresh interval */
1045 	if (provider->userinfo_refresh_interval > 0)
1046 		oidc_session_reset_userinfo_last_refresh(r, session);
1047 }
1048 
1049 /*
1050  * execute refresh token grant to refresh the existing access token
1051  */
oidc_refresh_access_token(request_rec * r,oidc_cfg * c,oidc_session_t * session,oidc_provider_t * provider,char ** new_access_token)1052 static apr_byte_t oidc_refresh_access_token(request_rec *r, oidc_cfg *c,
1053 		oidc_session_t *session, oidc_provider_t *provider,
1054 		char **new_access_token) {
1055 
1056 	oidc_debug(r, "enter");
1057 
1058 	/* get the refresh token that was stored in the session */
1059 	const char *refresh_token = oidc_session_get_refresh_token(r, session);
1060 	if (refresh_token == NULL) {
1061 		oidc_warn(r,
1062 				"refresh token routine called but no refresh_token found in the session");
1063 		return FALSE;
1064 	}
1065 
1066 	/* elements returned in the refresh response */
1067 	char *s_id_token = NULL;
1068 	int expires_in = -1;
1069 	char *s_token_type = NULL;
1070 	char *s_access_token = NULL;
1071 	char *s_refresh_token = NULL;
1072 
1073 	/* refresh the tokens by calling the token endpoint */
1074 	if (oidc_proto_refresh_request(r, c, provider, refresh_token, &s_id_token,
1075 			&s_access_token, &s_token_type, &expires_in, &s_refresh_token)
1076 			== FALSE) {
1077 		oidc_error(r, "access_token could not be refreshed");
1078 		return FALSE;
1079 	}
1080 
1081 	/* store the new access_token in the session and discard the old one */
1082 	oidc_session_set_access_token(r, session, s_access_token);
1083 	oidc_session_set_access_token_expires(r, session, expires_in);
1084 
1085 	/* reset the access token refresh timestamp */
1086 	oidc_session_reset_access_token_last_refresh(r, session);
1087 
1088 	/* see if we need to return it as a parameter */
1089 	if (new_access_token != NULL)
1090 		*new_access_token = s_access_token;
1091 
1092 	/* if we have a new refresh token (rolling refresh), store it in the session and overwrite the old one */
1093 	if (s_refresh_token != NULL)
1094 		oidc_session_set_refresh_token(r, session, s_refresh_token);
1095 
1096 	return TRUE;
1097 }
1098 
1099 /*
1100  * retrieve claims from the userinfo endpoint and return the stringified response
1101  */
oidc_retrieve_claims_from_userinfo_endpoint(request_rec * r,oidc_cfg * c,oidc_provider_t * provider,const char * access_token,oidc_session_t * session,char * id_token_sub,char ** userinfo_jwt)1102 static const char* oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r,
1103 		oidc_cfg *c, oidc_provider_t *provider, const char *access_token,
1104 		oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) {
1105 
1106 	oidc_debug(r, "enter");
1107 
1108 	char *result = NULL;
1109 	char *refreshed_access_token = NULL;
1110 
1111 	/* see if a userinfo endpoint is set, otherwise there's nothing to do for us */
1112 	if (provider->userinfo_endpoint_url == NULL) {
1113 		oidc_debug(r,
1114 				"not retrieving userinfo claims because userinfo_endpoint is not set");
1115 		return NULL;
1116 	}
1117 
1118 	/* see if there's an access token, otherwise we can't call the userinfo endpoint at all */
1119 	if (access_token == NULL) {
1120 		oidc_debug(r,
1121 				"not retrieving userinfo claims because access_token is not provided");
1122 		return NULL;
1123 	}
1124 
1125 	if ((id_token_sub == NULL) && (session != NULL)) {
1126 
1127 		// when refreshing claims from the userinfo endpoint
1128 		json_t *id_token_claims = oidc_session_get_idtoken_claims_json(r,
1129 				session);
1130 		if (id_token_claims == NULL) {
1131 			oidc_error(r, "no id_token_claims found in session");
1132 			return NULL;
1133 		}
1134 
1135 		oidc_jose_get_string(r->pool, id_token_claims, OIDC_CLAIM_SUB, FALSE,
1136 				&id_token_sub, NULL);
1137 	}
1138 
1139 	// TODO: return code should indicate whether the token expired or some other error occurred
1140 	// TODO: long-term: session storage should be JSON (with explicit types and less conversion, using standard routines)
1141 
1142 	/* try to get claims from the userinfo endpoint using the provided access token */
1143 	if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, access_token,
1144 			&result, userinfo_jwt) == FALSE) {
1145 
1146 		/* see if we have an existing session and we are refreshing the user info claims */
1147 		if (session != NULL) {
1148 
1149 			/* first call to user info endpoint failed, but the access token may have just expired, so refresh it */
1150 			if (oidc_refresh_access_token(r, c, session, provider,
1151 					&refreshed_access_token) == TRUE) {
1152 
1153 				/* try again with the new access token */
1154 				if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub,
1155 						refreshed_access_token, &result, userinfo_jwt)
1156 						== FALSE) {
1157 
1158 					oidc_error(r,
1159 							"resolving user info claims with the refreshed access token failed, nothing will be stored in the session");
1160 					result = NULL;
1161 
1162 				}
1163 
1164 			} else {
1165 
1166 				oidc_warn(r,
1167 						"refreshing access token failed, claims will not be retrieved/refreshed from the userinfo endpoint");
1168 				result = NULL;
1169 
1170 			}
1171 
1172 		} else {
1173 
1174 			oidc_error(r,
1175 					"resolving user info claims with the existing/provided access token failed, nothing will be stored in the session");
1176 			result = NULL;
1177 
1178 		}
1179 	}
1180 
1181 	return result;
1182 }
1183 
1184 /*
1185  * get (new) claims from the userinfo endpoint
1186  */
oidc_refresh_claims_from_userinfo_endpoint(request_rec * r,oidc_cfg * cfg,oidc_session_t * session)1187 static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r,
1188 		oidc_cfg *cfg, oidc_session_t *session) {
1189 
1190 	oidc_provider_t *provider = NULL;
1191 	const char *claims = NULL;
1192 	const char *access_token = NULL;
1193 	char *userinfo_jwt = NULL;
1194 
1195 	/* get the current provider info */
1196 	if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE)
1197 		return FALSE;
1198 
1199 	/* see if we can do anything here, i.e. we have a userinfo endpoint and a refresh interval is configured */
1200 	apr_time_t interval = apr_time_from_sec(
1201 			provider->userinfo_refresh_interval);
1202 
1203 	oidc_debug(r, "userinfo_endpoint=%s, interval=%d",
1204 			provider->userinfo_endpoint_url,
1205 			provider->userinfo_refresh_interval);
1206 
1207 	if ((provider->userinfo_endpoint_url != NULL) && (interval > 0)) {
1208 
1209 		/* get the last refresh timestamp from the session info */
1210 		apr_time_t last_refresh = oidc_session_get_userinfo_last_refresh(r,
1211 				session);
1212 
1213 		oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
1214 				apr_time_sec(last_refresh + interval - apr_time_now()));
1215 
1216 		/* see if we need to refresh again */
1217 		if (last_refresh + interval < apr_time_now()) {
1218 
1219 			/* get the current access token */
1220 			access_token = oidc_session_get_access_token(r, session);
1221 
1222 			/* retrieve the current claims */
1223 			claims = oidc_retrieve_claims_from_userinfo_endpoint(r, cfg,
1224 					provider, access_token, session, NULL, &userinfo_jwt);
1225 
1226 			/* store claims resolved from userinfo endpoint */
1227 			oidc_store_userinfo_claims(r, cfg, session, provider, claims,
1228 					userinfo_jwt);
1229 
1230 			/* indicated something changed */
1231 			return TRUE;
1232 		}
1233 	}
1234 	return FALSE;
1235 }
1236 
1237 /*
1238  * copy the claims and id_token from the session to the request state and optionally return them
1239  */
oidc_copy_tokens_to_request_state(request_rec * r,oidc_session_t * session,const char ** s_id_token,const char ** s_claims)1240 static void oidc_copy_tokens_to_request_state(request_rec *r,
1241 		oidc_session_t *session, const char **s_id_token, const char **s_claims) {
1242 
1243 	const char *id_token = oidc_session_get_idtoken_claims(r, session);
1244 	const char *claims = oidc_session_get_userinfo_claims(r, session);
1245 
1246 	oidc_debug(r, "id_token=%s claims=%s", id_token, claims);
1247 
1248 	if (id_token != NULL) {
1249 		oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token);
1250 		if (s_id_token != NULL)
1251 			*s_id_token = id_token;
1252 	}
1253 
1254 	if (claims != NULL) {
1255 		oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims);
1256 		if (s_claims != NULL)
1257 			*s_claims = claims;
1258 	}
1259 }
1260 
1261 /*
1262  * pass refresh_token, access_token and access_token_expires as headers/environment variables to the application
1263  */
oidc_session_pass_tokens(request_rec * r,oidc_cfg * cfg,oidc_session_t * session,apr_byte_t * needs_save)1264 static apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg *cfg,
1265 		oidc_session_t *session, apr_byte_t *needs_save) {
1266 
1267 	apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
1268 	apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
1269 	apr_byte_t pass_base64url = oidc_cfg_dir_pass_info_base64url(r);
1270 
1271 	/* set the refresh_token in the app headers/variables, if enabled for this location/directory */
1272 	const char *refresh_token = oidc_session_get_refresh_token(r, session);
1273 	if ((oidc_cfg_dir_pass_refresh_token(r) != 0) && (refresh_token != NULL)) {
1274 		/* pass it to the app in a header or environment variable */
1275 		oidc_util_set_app_info(r, OIDC_APP_INFO_REFRESH_TOKEN, refresh_token,
1276 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars, pass_base64url);
1277 	}
1278 
1279 	/* set the access_token in the app headers/variables */
1280 	const char *access_token = oidc_session_get_access_token(r, session);
1281 	if (access_token != NULL) {
1282 		/* pass it to the app in a header or environment variable */
1283 		oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token,
1284 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars, pass_base64url);
1285 	}
1286 
1287 	/* set the expiry timestamp in the app headers/variables */
1288 	const char *access_token_expires = oidc_session_get_access_token_expires(r,
1289 			session);
1290 	if (access_token_expires != NULL) {
1291 		/* pass it to the app in a header or environment variable */
1292 		oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_EXP,
1293 				access_token_expires,
1294 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars,
1295 				pass_base64url);
1296 	}
1297 
1298 	/*
1299 	 * reset the session inactivity timer
1300 	 * but only do this once per 10% of the inactivity timeout interval (with a max to 60 seconds)
1301 	 * for performance reasons
1302 	 *
1303 	 * now there's a small chance that the session ends 10% (or a minute) earlier than configured/expected
1304 	 * cq. when there's a request after a recent save (so no update) and then no activity happens until
1305 	 * a request comes in just before the session should expire
1306 	 * ("recent" and "just before" refer to 10%-with-a-max-of-60-seconds of the inactivity interval after
1307 	 * the start/last-update and before the expiry of the session respectively)
1308 	 *
1309 	 * this is be deemed acceptable here because of performance gain
1310 	 */
1311 	apr_time_t interval = apr_time_from_sec(cfg->session_inactivity_timeout);
1312 	apr_time_t now = apr_time_now();
1313 	apr_time_t slack = interval / 10;
1314 	if (slack > apr_time_from_sec(60))
1315 		slack = apr_time_from_sec(60);
1316 	if (session->expiry - now < interval - slack) {
1317 		session->expiry = now + interval;
1318 		*needs_save = TRUE;
1319 	}
1320 
1321 	/* log message about session expiry */
1322 	oidc_log_session_expires(r, "session inactivity timeout", session->expiry);
1323 
1324 	return TRUE;
1325 }
1326 
oidc_refresh_access_token_before_expiry(request_rec * r,oidc_cfg * cfg,oidc_session_t * session,int ttl_minimum,int logout_on_error)1327 static apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r,
1328 		oidc_cfg *cfg, oidc_session_t *session, int ttl_minimum,
1329 		int logout_on_error) {
1330 
1331 	const char *s_access_token_expires = NULL;
1332 	apr_time_t t_expires = -1;
1333 	oidc_provider_t *provider = NULL;
1334 
1335 	oidc_debug(r, "ttl_minimum=%d", ttl_minimum);
1336 
1337 	if (ttl_minimum < 0)
1338 		return FALSE;
1339 
1340 	s_access_token_expires = oidc_session_get_access_token_expires(r, session);
1341 	if (s_access_token_expires == NULL) {
1342 		oidc_debug(r,
1343 				"no access token expires_in stored in the session (i.e. returned from in the authorization response), so cannot refresh the access token based on TTL requirement");
1344 		return FALSE;
1345 	}
1346 
1347 	if (oidc_session_get_refresh_token(r, session) == NULL) {
1348 		oidc_debug(r,
1349 				"no refresh token stored in the session, so cannot refresh the access token based on TTL requirement");
1350 		return FALSE;
1351 	}
1352 
1353 	if (sscanf(s_access_token_expires, "%" APR_TIME_T_FMT, &t_expires) != 1) {
1354 		oidc_error(r, "could not parse s_access_token_expires %s",
1355 				s_access_token_expires);
1356 		return FALSE;
1357 	}
1358 
1359 	t_expires = apr_time_from_sec(t_expires - ttl_minimum);
1360 
1361 	oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
1362 			apr_time_sec(t_expires - apr_time_now()));
1363 
1364 	if (t_expires > apr_time_now())
1365 		return FALSE;
1366 
1367 	if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE)
1368 		return FALSE;
1369 
1370 	if (oidc_refresh_access_token(r, cfg, session, provider,
1371 			NULL) == FALSE) {
1372 		oidc_warn(r, "access_token could not be refreshed, logout=%d",
1373 				logout_on_error & OIDC_LOGOUT_ON_ERROR_REFRESH);
1374 		if (logout_on_error & OIDC_LOGOUT_ON_ERROR_REFRESH)
1375 			return OIDC_REFRESH_ERROR;
1376 		else
1377 			return FALSE;
1378 	}
1379 
1380 	return TRUE;
1381 }
1382 
1383 /*
1384  * handle the case where we have identified an existing authentication session for a user
1385  */
oidc_handle_existing_session(request_rec * r,oidc_cfg * cfg,oidc_session_t * session,apr_byte_t * needs_save)1386 static int oidc_handle_existing_session(request_rec *r, oidc_cfg *cfg,
1387 		oidc_session_t *session, apr_byte_t *needs_save) {
1388 
1389 	apr_byte_t rv = FALSE;
1390 
1391 	oidc_debug(r, "enter");
1392 
1393 	/* set the user in the main request for further (incl. sub-request) processing */
1394 	r->user = apr_pstrdup(r->pool, session->remote_user);
1395 	oidc_debug(r, "set remote_user to \"%s\"", r->user);
1396 
1397 	/* get the header name in which the remote user name needs to be passed */
1398 	char *authn_header = oidc_cfg_dir_authn_header(r);
1399 	apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
1400 	apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
1401 	apr_byte_t pass_base64url = oidc_cfg_dir_pass_info_base64url(r);
1402 
1403 	/* verify current cookie domain against issued cookie domain */
1404 	if (oidc_check_cookie_domain(r, cfg, session) == FALSE)
1405 		return HTTP_UNAUTHORIZED;
1406 
1407 	/* check if the maximum session duration was exceeded */
1408 	int rc = oidc_check_max_session_duration(r, cfg, session);
1409 	if (rc != OK)
1410 		return rc;
1411 
1412 	/* if needed, refresh the access token */
1413 	rv = oidc_refresh_access_token_before_expiry(r, cfg, session,
1414 			oidc_cfg_dir_refresh_access_token_before_expiry(r),
1415 			oidc_cfg_dir_logout_on_error_refresh(r));
1416 
1417 	if (rv == OIDC_REFRESH_ERROR) {
1418 		*needs_save = FALSE;
1419 		return oidc_handle_logout_request(r, cfg, session, cfg->default_slo_url);
1420 	}
1421 
1422 	*needs_save |= rv;
1423 
1424 	/* if needed, refresh claims from the user info endpoint */
1425 	if (oidc_refresh_claims_from_userinfo_endpoint(r, cfg, session) == TRUE)
1426 		*needs_save = TRUE;
1427 
1428 	/*
1429 	 * we're going to pass the information that we have to the application,
1430 	 * but first we need to scrub the headers that we're going to use for security reasons
1431 	 */
1432 	oidc_scrub_headers(r);
1433 
1434 	/* set the user authentication HTTP header if set and required */
1435 	if ((r->user != NULL) && (authn_header != NULL))
1436 		oidc_util_hdr_in_set(r, authn_header, r->user);
1437 
1438 	const char *s_claims = NULL;
1439 	const char *s_id_token = NULL;
1440 
1441 	/* copy id_token and claims from session to request state and obtain their values */
1442 	oidc_copy_tokens_to_request_state(r, session, &s_id_token, &s_claims);
1443 
1444 	if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_CLAIMS)) {
1445 		/* set the userinfo claims in the app headers */
1446 		if (oidc_set_app_claims(r, cfg, session, s_claims) == FALSE)
1447 			return HTTP_INTERNAL_SERVER_ERROR;
1448 	}
1449 
1450 	if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JSON_OBJECT)) {
1451 		/* pass the userinfo JSON object to the app in a header or environment variable */
1452 		oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JSON, s_claims,
1453 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars, pass_base64url);
1454 	}
1455 
1456 	if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JWT)) {
1457 		if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
1458 			/* get the compact serialized JWT from the session */
1459 			const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r,
1460 					session);
1461 			if (s_userinfo_jwt != NULL) {
1462 				/* pass the compact serialized JWT to the app in a header or environment variable */
1463 				oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JWT,
1464 						s_userinfo_jwt,
1465 						OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars,
1466 						pass_base64url);
1467 			} else {
1468 				oidc_debug(r,
1469 						"configured to pass userinfo in a JWT, but no such JWT was found in the session (probably no such JWT was returned from the userinfo endpoint)");
1470 			}
1471 		} else {
1472 			oidc_error(r,
1473 					"session type \"client-cookie\" does not allow storing/passing a userinfo JWT; use \"" OIDCSessionType " server-cache\" for that");
1474 		}
1475 	}
1476 
1477 	if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) {
1478 		/* set the id_token in the app headers */
1479 		if (oidc_set_app_claims(r, cfg, session, s_id_token) == FALSE)
1480 			return HTTP_INTERNAL_SERVER_ERROR;
1481 	}
1482 
1483 	if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) {
1484 		/* pass the id_token JSON object to the app in a header or environment variable */
1485 		oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN_PAYLOAD, s_id_token,
1486 				OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars, pass_base64url);
1487 	}
1488 
1489 	if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) {
1490 		if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
1491 			/* get the compact serialized JWT from the session */
1492 			const char *s_id_token = oidc_session_get_idtoken(r, session);
1493 			/* pass the compact serialized JWT to the app in a header or environment variable */
1494 			oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN, s_id_token,
1495 					OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars,
1496 					pass_base64url);
1497 		} else {
1498 			oidc_error(r,
1499 					"session type \"client-cookie\" does not allow storing/passing the id_token; use \"" OIDCSessionType " server-cache\" for that");
1500 		}
1501 	}
1502 
1503 	/* pass the at, rt and at expiry to the application, possibly update the session expiry */
1504 	if (oidc_session_pass_tokens(r, cfg, session, needs_save) == FALSE)
1505 		return HTTP_INTERNAL_SERVER_ERROR;
1506 
1507 	/* return "user authenticated" status */
1508 	return OK;
1509 }
1510 
1511 /*
1512  * helper function for basic/implicit client flows upon receiving an authorization response:
1513  * check that it matches the state stored in the browser and return the variables associated
1514  * with the state, such as original_url and OP oidc_provider_t pointer.
1515  */
oidc_authorization_response_match_state(request_rec * r,oidc_cfg * c,const char * state,struct oidc_provider_t ** provider,oidc_proto_state_t ** proto_state)1516 static apr_byte_t oidc_authorization_response_match_state(request_rec *r,
1517 		oidc_cfg *c, const char *state, struct oidc_provider_t **provider,
1518 		oidc_proto_state_t **proto_state) {
1519 
1520 	oidc_debug(r, "enter (state=%s)", state);
1521 
1522 	if ((state == NULL) || (apr_strnatcmp(state, "") == 0)) {
1523 		oidc_error(r, "state parameter is not set");
1524 		return FALSE;
1525 	}
1526 
1527 	/* check the state parameter against what we stored in a cookie */
1528 	if (oidc_restore_proto_state(r, c, state, proto_state) == FALSE) {
1529 		oidc_error(r, "unable to restore state");
1530 		return FALSE;
1531 	}
1532 
1533 	*provider = oidc_get_provider_for_issuer(r, c,
1534 			oidc_proto_state_get_issuer(*proto_state), FALSE);
1535 
1536 	return (*provider != NULL);
1537 }
1538 
1539 /*
1540  * redirect the browser to the session logout endpoint
1541  */
oidc_session_redirect_parent_window_to_logout(request_rec * r,oidc_cfg * c)1542 static int oidc_session_redirect_parent_window_to_logout(request_rec *r,
1543 		oidc_cfg *c) {
1544 
1545 	oidc_debug(r, "enter");
1546 
1547 	char *java_script = apr_psprintf(r->pool,
1548 			"    <script type=\"text/javascript\">\n"
1549 			"      window.top.location.href = '%s?session=logout';\n"
1550 			"    </script>\n", oidc_get_redirect_uri(r, c));
1551 
1552 	return oidc_util_html_send(r, "Redirecting...", java_script, NULL, NULL,
1553 			OK);
1554 }
1555 
1556 /*
1557  * handle an error returned by the OP
1558  */
oidc_authorization_response_error(request_rec * r,oidc_cfg * c,oidc_proto_state_t * proto_state,const char * error,const char * error_description)1559 static int oidc_authorization_response_error(request_rec *r, oidc_cfg *c,
1560 		oidc_proto_state_t *proto_state, const char *error,
1561 		const char *error_description) {
1562 	const char *prompt = oidc_proto_state_get_prompt(proto_state);
1563 	if (prompt != NULL)
1564 		prompt = apr_pstrdup(r->pool, prompt);
1565 	oidc_proto_state_destroy(proto_state);
1566 	if ((prompt != NULL)
1567 			&& (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
1568 		return oidc_session_redirect_parent_window_to_logout(r, c);
1569 	}
1570 	return oidc_util_html_send_error(r, c->error_template,
1571 			apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error),
1572 			error_description, OK);
1573 }
1574 
1575 /*
1576  * get the r->user for this request based on the configuration for OIDC/OAuth
1577  */
oidc_get_remote_user(request_rec * r,const char * claim_name,const char * reg_exp,const char * replace,json_t * json,char ** request_user)1578 apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name,
1579 		const char *reg_exp, const char *replace, json_t *json,
1580 		char **request_user) {
1581 
1582 	/* get the claim value from the JSON object */
1583 	json_t *username = json_object_get(json, claim_name);
1584 	if ((username == NULL) || (!json_is_string(username))) {
1585 		oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name);
1586 		return FALSE;
1587 	}
1588 
1589 	*request_user = apr_pstrdup(r->pool, json_string_value(username));
1590 
1591 	if (reg_exp != NULL) {
1592 
1593 		char *error_str = NULL;
1594 
1595 		if (replace == NULL) {
1596 
1597 			if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp,
1598 					request_user, &error_str) == FALSE) {
1599 				oidc_error(r, "oidc_util_regexp_first_match failed: %s",
1600 						error_str);
1601 				*request_user = NULL;
1602 				return FALSE;
1603 			}
1604 
1605 		} else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp,
1606 				replace, request_user, &error_str) == FALSE) {
1607 
1608 			oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str);
1609 			*request_user = NULL;
1610 			return FALSE;
1611 		}
1612 
1613 	}
1614 
1615 	return TRUE;
1616 }
1617 
1618 /*
1619  * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
1620  */
oidc_set_request_user(request_rec * r,oidc_cfg * c,oidc_provider_t * provider,oidc_jwt_t * jwt,const char * s_claims)1621 static apr_byte_t oidc_set_request_user(request_rec *r, oidc_cfg *c,
1622 		oidc_provider_t *provider, oidc_jwt_t *jwt, const char *s_claims) {
1623 
1624 	char *issuer = provider->issuer;
1625 	char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim.claim_name);
1626 	int n = strlen(claim_name);
1627 	apr_byte_t post_fix_with_issuer = (claim_name[n - 1] == OIDC_CHAR_AT);
1628 	if (post_fix_with_issuer == TRUE) {
1629 		claim_name[n - 1] = '\0';
1630 		issuer =
1631 				(strstr(issuer, "https://") == NULL) ?
1632 						apr_pstrdup(r->pool, issuer) :
1633 						apr_pstrdup(r->pool, issuer + strlen("https://"));
1634 	}
1635 
1636 	/* extract the username claim (default: "sub") from the id_token payload or user claims */
1637 	apr_byte_t rc = FALSE;
1638 	char *remote_user = NULL;
1639 	json_t *claims = NULL;
1640 	oidc_util_decode_json_object(r, s_claims, &claims);
1641 	if (claims == NULL) {
1642 		rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp,
1643 				c->remote_user_claim.replace, jwt->payload.value.json,
1644 				&remote_user);
1645 	} else {
1646 		oidc_util_json_merge(r, jwt->payload.value.json, claims);
1647 		rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp,
1648 				c->remote_user_claim.replace, claims, &remote_user);
1649 		json_decref(claims);
1650 	}
1651 
1652 	if ((rc == FALSE) || (remote_user == NULL)) {
1653 		oidc_error(r,
1654 				"" OIDCRemoteUserClaim "is set to \"%s\", but could not set the remote user based on the requested claim \"%s\" and the available claims for the user",
1655 				c->remote_user_claim.claim_name, claim_name);
1656 		return FALSE;
1657 	}
1658 
1659 	if (post_fix_with_issuer == TRUE)
1660 		remote_user = apr_psprintf(r->pool, "%s%s%s", remote_user, OIDC_STR_AT,
1661 				issuer);
1662 
1663 	r->user = apr_pstrdup(r->pool, remote_user);
1664 
1665 	oidc_debug(r, "set remote_user to \"%s\" based on claim: \"%s\"%s", r->user,
1666 			c->remote_user_claim.claim_name,
1667 			c->remote_user_claim.reg_exp ?
1668 					apr_psprintf(r->pool,
1669 							" and expression: \"%s\" and replace string: \"%s\"",
1670 							c->remote_user_claim.reg_exp,
1671 							c->remote_user_claim.replace) :
1672 							"");
1673 
1674 	return TRUE;
1675 }
1676 
oidc_make_sid_iss_unique(request_rec * r,const char * sid,const char * issuer)1677 static char* oidc_make_sid_iss_unique(request_rec *r, const char *sid,
1678 		const char *issuer) {
1679 	return apr_psprintf(r->pool, "%s@%s", sid, issuer);
1680 }
1681 
1682 /*
1683  * store resolved information in the session
1684  */
oidc_save_in_session(request_rec * r,oidc_cfg * c,oidc_session_t * session,oidc_provider_t * provider,const char * remoteUser,const char * id_token,oidc_jwt_t * id_token_jwt,const char * claims,const char * access_token,const int expires_in,const char * refresh_token,const char * session_state,const char * state,const char * original_url,const char * userinfo_jwt)1685 static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *c,
1686 		oidc_session_t *session, oidc_provider_t *provider,
1687 		const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt,
1688 		const char *claims, const char *access_token, const int expires_in,
1689 		const char *refresh_token, const char *session_state, const char *state,
1690 		const char *original_url, const char *userinfo_jwt) {
1691 
1692 	/* store the user in the session */
1693 	session->remote_user = remoteUser;
1694 
1695 	/* set the session expiry to the inactivity timeout */
1696 	session->expiry =
1697 			apr_time_now() + apr_time_from_sec(c->session_inactivity_timeout);
1698 
1699 	/* store the claims payload in the id_token for later reference */
1700 	oidc_session_set_idtoken_claims(r, session,
1701 			id_token_jwt->payload.value.str);
1702 
1703 	if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
1704 		/* store the compact serialized representation of the id_token for later reference  */
1705 		oidc_session_set_idtoken(r, session, id_token);
1706 	}
1707 
1708 	/* store the issuer in the session (at least needed for session mgmt and token refresh */
1709 	oidc_session_set_issuer(r, session, provider->issuer);
1710 
1711 	/* store the state and original URL in the session for handling browser-back more elegantly */
1712 	oidc_session_set_request_state(r, session, state);
1713 	oidc_session_set_original_url(r, session, original_url);
1714 
1715 	if ((session_state != NULL) && (provider->check_session_iframe != NULL)) {
1716 		/* store the session state and required parameters session management  */
1717 		oidc_session_set_session_state(r, session, session_state);
1718 		oidc_debug(r,
1719 				"session management enabled: stored session_state (%s), check_session_iframe (%s) and client_id (%s) in the session",
1720 				session_state, provider->check_session_iframe,
1721 				provider->client_id);
1722 	} else if (provider->check_session_iframe == NULL) {
1723 		oidc_debug(r,
1724 				"session management disabled: \"check_session_iframe\" is not set in provider configuration");
1725 	} else {
1726 		oidc_debug(r,
1727 				"session management disabled: no \"session_state\" value is provided in the authentication response even though \"check_session_iframe\" (%s) is set in the provider configuration",
1728 				provider->check_session_iframe);
1729 	}
1730 
1731 	/* store claims resolved from userinfo endpoint */
1732 	oidc_store_userinfo_claims(r, c, session, provider, claims, userinfo_jwt);
1733 
1734 	/* see if we have an access_token */
1735 	if (access_token != NULL) {
1736 		/* store the access_token in the session context */
1737 		oidc_session_set_access_token(r, session, access_token);
1738 		/* store the associated expires_in value */
1739 		oidc_session_set_access_token_expires(r, session, expires_in);
1740 		/* reset the access token refresh timestamp */
1741 		oidc_session_reset_access_token_last_refresh(r, session);
1742 	}
1743 
1744 	/* see if we have a refresh_token */
1745 	if (refresh_token != NULL) {
1746 		/* store the refresh_token in the session context */
1747 		oidc_session_set_refresh_token(r, session, refresh_token);
1748 	}
1749 
1750 	/* store max session duration in the session as a hard cut-off expiry timestamp */
1751 	apr_time_t session_expires =
1752 			(provider->session_max_duration == 0) ?
1753 					apr_time_from_sec(id_token_jwt->payload.exp) :
1754 					(apr_time_now()
1755 							+ apr_time_from_sec(provider->session_max_duration));
1756 	oidc_session_set_session_expires(r, session, session_expires);
1757 
1758 	oidc_debug(r,
1759 			"provider->session_max_duration = %d, session_expires=%" APR_TIME_T_FMT,
1760 			provider->session_max_duration, session_expires);
1761 
1762 	/* log message about max session duration */
1763 	oidc_log_session_expires(r, "session max lifetime", session_expires);
1764 
1765 	/* store the domain for which this session is valid */
1766 	oidc_session_set_cookie_domain(r, session,
1767 			c->cookie_domain ? c->cookie_domain : oidc_get_current_url_host(r));
1768 
1769 	char *sid = NULL;
1770 	oidc_debug(r, "provider->backchannel_logout_supported=%d",
1771 			provider->backchannel_logout_supported);
1772 	if (provider->backchannel_logout_supported > 0) {
1773 		oidc_jose_get_string(r->pool, id_token_jwt->payload.value.json,
1774 				OIDC_CLAIM_SID, FALSE, &sid, NULL);
1775 		if (sid == NULL)
1776 			sid = id_token_jwt->payload.sub;
1777 		session->sid = oidc_make_sid_iss_unique(r, sid, provider->issuer);
1778 	}
1779 
1780 	/* store the session */
1781 	return oidc_session_save(r, session, TRUE);
1782 }
1783 
1784 /*
1785  * parse the expiry for the access token
1786  */
oidc_parse_expires_in(request_rec * r,const char * expires_in)1787 static int oidc_parse_expires_in(request_rec *r, const char *expires_in) {
1788 	if (expires_in != NULL) {
1789 		char *ptr = NULL;
1790 		long number = strtol(expires_in, &ptr, 10);
1791 		if (number <= 0) {
1792 			oidc_warn(r,
1793 					"could not convert \"expires_in\" value (%s) to a number",
1794 					expires_in);
1795 			return -1;
1796 		}
1797 		return number;
1798 	}
1799 	return -1;
1800 }
1801 
1802 /*
1803  * handle the different flows (hybrid, implicit, Authorization Code)
1804  */
oidc_handle_flows(request_rec * r,oidc_cfg * c,oidc_proto_state_t * proto_state,oidc_provider_t * provider,apr_table_t * params,const char * response_mode,oidc_jwt_t ** jwt)1805 static apr_byte_t oidc_handle_flows(request_rec *r, oidc_cfg *c,
1806 		oidc_proto_state_t *proto_state, oidc_provider_t *provider,
1807 		apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
1808 
1809 	apr_byte_t rc = FALSE;
1810 
1811 	const char *requested_response_type = oidc_proto_state_get_response_type(
1812 			proto_state);
1813 
1814 	/* handle the requested response type/mode */
1815 	if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1816 			OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN)) {
1817 		rc = oidc_proto_authorization_response_code_idtoken_token(r, c,
1818 				proto_state, provider, params, response_mode, jwt);
1819 	} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1820 			OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN)) {
1821 		rc = oidc_proto_authorization_response_code_idtoken(r, c, proto_state,
1822 				provider, params, response_mode, jwt);
1823 	} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1824 			OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN)) {
1825 		rc = oidc_proto_handle_authorization_response_code_token(r, c,
1826 				proto_state, provider, params, response_mode, jwt);
1827 	} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1828 			OIDC_PROTO_RESPONSE_TYPE_CODE)) {
1829 		rc = oidc_proto_handle_authorization_response_code(r, c, proto_state,
1830 				provider, params, response_mode, jwt);
1831 	} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1832 			OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN)) {
1833 		rc = oidc_proto_handle_authorization_response_idtoken_token(r, c,
1834 				proto_state, provider, params, response_mode, jwt);
1835 	} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
1836 			OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) {
1837 		rc = oidc_proto_handle_authorization_response_idtoken(r, c, proto_state,
1838 				provider, params, response_mode, jwt);
1839 	} else {
1840 		oidc_error(r, "unsupported response type: \"%s\"",
1841 				requested_response_type);
1842 	}
1843 
1844 	if ((rc == FALSE) && (*jwt != NULL)) {
1845 		oidc_jwt_destroy(*jwt);
1846 		*jwt = NULL;
1847 	}
1848 
1849 	return rc;
1850 }
1851 
1852 /* handle the browser back on an authorization response */
oidc_handle_browser_back(request_rec * r,const char * r_state,oidc_session_t * session)1853 static apr_byte_t oidc_handle_browser_back(request_rec *r, const char *r_state,
1854 		oidc_session_t *session) {
1855 
1856 	/*  see if we have an existing session and browser-back was used */
1857 	const char *s_state = NULL, *o_url = NULL;
1858 
1859 	if (session->remote_user != NULL) {
1860 
1861 		s_state = oidc_session_get_request_state(r, session);
1862 		o_url = oidc_session_get_original_url(r, session);
1863 
1864 		if ((r_state != NULL) && (s_state != NULL)
1865 				&& (apr_strnatcmp(r_state, s_state) == 0)) {
1866 
1867 			/* log the browser back event detection */
1868 			oidc_warn(r,
1869 					"browser back detected, redirecting to original URL: %s",
1870 					o_url);
1871 
1872 			/* go back to the URL that he originally tried to access */
1873 			oidc_util_hdr_out_location_set(r, o_url);
1874 
1875 			return TRUE;
1876 		}
1877 	}
1878 
1879 	return FALSE;
1880 }
1881 
1882 /*
1883  * complete the handling of an authorization response by obtaining, parsing and verifying the
1884  * id_token and storing the authenticated user state in the session
1885  */
oidc_handle_authorization_response(request_rec * r,oidc_cfg * c,oidc_session_t * session,apr_table_t * params,const char * response_mode)1886 static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c,
1887 		oidc_session_t *session, apr_table_t *params, const char *response_mode) {
1888 
1889 	oidc_debug(r, "enter, response_mode=%s", response_mode);
1890 
1891 	oidc_provider_t *provider = NULL;
1892 	oidc_proto_state_t *proto_state = NULL;
1893 	oidc_jwt_t *jwt = NULL;
1894 
1895 	/* see if this response came from a browser-back event */
1896 	if (oidc_handle_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE),
1897 			session) == TRUE)
1898 		return HTTP_MOVED_TEMPORARILY;
1899 
1900 	/* match the returned state parameter against the state stored in the browser */
1901 	if (oidc_authorization_response_match_state(r, c,
1902 			apr_table_get(params, OIDC_PROTO_STATE), &provider, &proto_state)
1903 			== FALSE) {
1904 		if (c->default_sso_url != NULL) {
1905 			oidc_warn(r,
1906 					"invalid authorization response state; a default SSO URL is set, sending the user there: %s",
1907 					c->default_sso_url);
1908 			oidc_util_hdr_out_location_set(r, c->default_sso_url);
1909 			return HTTP_MOVED_TEMPORARILY;
1910 		}
1911 		oidc_error(r,
1912 				"invalid authorization response state and no default SSO URL is set, sending an error...");
1913 		// if content was already returned via html/http send then don't return 500
1914 		// but send 200 to avoid extraneous internal error document text to be sent
1915 		return ((r->user) && (strncmp(r->user, "", 1) == 0)) ?
1916 				OK :
1917 				HTTP_BAD_REQUEST;
1918 	}
1919 
1920 	/* see if the response is an error response */
1921 	if (apr_table_get(params, OIDC_PROTO_ERROR) != NULL)
1922 		return oidc_authorization_response_error(r, c, proto_state,
1923 				apr_table_get(params, OIDC_PROTO_ERROR),
1924 				apr_table_get(params, OIDC_PROTO_ERROR_DESCRIPTION));
1925 
1926 	/* handle the code, implicit or hybrid flow */
1927 	if (oidc_handle_flows(r, c, proto_state, provider, params, response_mode,
1928 			&jwt) == FALSE)
1929 		return oidc_authorization_response_error(r, c, proto_state,
1930 				"Error in handling response type.", NULL);
1931 
1932 	if (jwt == NULL) {
1933 		oidc_error(r, "no id_token was provided");
1934 		return oidc_authorization_response_error(r, c, proto_state,
1935 				"No id_token was provided.", NULL);
1936 	}
1937 
1938 	int expires_in = oidc_parse_expires_in(r,
1939 			apr_table_get(params, OIDC_PROTO_EXPIRES_IN));
1940 	char *userinfo_jwt = NULL;
1941 
1942 	/*
1943 	 * optionally resolve additional claims against the userinfo endpoint
1944 	 * parsed claims are not actually used here but need to be parsed anyway for error checking purposes
1945 	 */
1946 	const char *claims = oidc_retrieve_claims_from_userinfo_endpoint(r, c,
1947 			provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL,
1948 			jwt->payload.sub, &userinfo_jwt);
1949 
1950 	/* restore the original protected URL that the user was trying to access */
1951 	const char *original_url = oidc_proto_state_get_original_url(proto_state);
1952 	if (original_url != NULL)
1953 		original_url = apr_pstrdup(r->pool, original_url);
1954 	const char *original_method = oidc_proto_state_get_original_method(
1955 			proto_state);
1956 	if (original_method != NULL)
1957 		original_method = apr_pstrdup(r->pool, original_method);
1958 	const char *prompt = oidc_proto_state_get_prompt(proto_state);
1959 
1960 	/* set the user */
1961 	if (oidc_set_request_user(r, c, provider, jwt, claims) == TRUE) {
1962 
1963 		/* session management: if the user in the new response is not equal to the old one, error out */
1964 		if ((prompt != NULL)
1965 				&& (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
1966 			// TOOD: actually need to compare sub? (need to store it in the session separately then
1967 			//const char *sub = NULL;
1968 			//oidc_session_get(r, session, "sub", &sub);
1969 			//if (apr_strnatcmp(sub, jwt->payload.sub) != 0) {
1970 			if (apr_strnatcmp(session->remote_user, r->user) != 0) {
1971 				oidc_warn(r,
1972 						"user set from new id_token is different from current one");
1973 				oidc_jwt_destroy(jwt);
1974 				return oidc_authorization_response_error(r, c, proto_state,
1975 						"User changed!", NULL);
1976 			}
1977 		}
1978 
1979 		/* store resolved information in the session */
1980 		if (oidc_save_in_session(r, c, session, provider, r->user,
1981 				apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims,
1982 				apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), expires_in,
1983 				apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN),
1984 				apr_table_get(params, OIDC_PROTO_SESSION_STATE),
1985 				apr_table_get(params, OIDC_PROTO_STATE), original_url,
1986 				userinfo_jwt) == FALSE)
1987 			return HTTP_INTERNAL_SERVER_ERROR;
1988 
1989 	} else {
1990 		oidc_error(r, "remote user could not be set");
1991 		return oidc_authorization_response_error(r, c, proto_state,
1992 				"Remote user could not be set: contact the website administrator",
1993 				NULL);
1994 	}
1995 
1996 	/* cleanup */
1997 	oidc_proto_state_destroy(proto_state);
1998 	oidc_jwt_destroy(jwt);
1999 
2000 	/* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */
2001 	if (r->user == NULL)
2002 		return HTTP_UNAUTHORIZED;
2003 
2004 	/* log the successful response */
2005 	oidc_debug(r,
2006 			"session created and stored, returning to original URL: %s, original method: %s",
2007 			original_url, original_method);
2008 
2009 	/* check whether form post data was preserved; if so restore it */
2010 	if (apr_strnatcmp(original_method, OIDC_METHOD_FORM_POST) == 0) {
2011 		return oidc_request_post_preserved_restore(r, original_url);
2012 	}
2013 
2014 	/* now we've authenticated the user so go back to the URL that he originally tried to access */
2015 	oidc_util_hdr_out_location_set(r, original_url);
2016 
2017 	/* do the actual redirect to the original URL */
2018 	return HTTP_MOVED_TEMPORARILY;
2019 }
2020 
2021 /*
2022  * handle an OpenID Connect Authorization Response using the POST (+fragment->POST) response_mode
2023  */
oidc_handle_post_authorization_response(request_rec * r,oidc_cfg * c,oidc_session_t * session)2024 static int oidc_handle_post_authorization_response(request_rec *r, oidc_cfg *c,
2025 		oidc_session_t *session) {
2026 
2027 	oidc_debug(r, "enter");
2028 
2029 	/* initialize local variables */
2030 	char *response_mode = NULL;
2031 
2032 	/* read the parameters that are POST-ed to us */
2033 	apr_table_t *params = apr_table_make(r->pool, 8);
2034 	if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) {
2035 		oidc_error(r, "something went wrong when reading the POST parameters");
2036 		return HTTP_INTERNAL_SERVER_ERROR;
2037 	}
2038 
2039 	/* see if we've got any POST-ed data at all */
2040 	if ((apr_table_elts(params)->nelts < 1)
2041 			|| ((apr_table_elts(params)->nelts == 1)
2042 					&& apr_table_get(params, OIDC_PROTO_RESPONSE_MODE)
2043 					&& (apr_strnatcmp(
2044 							apr_table_get(params, OIDC_PROTO_RESPONSE_MODE),
2045 							OIDC_PROTO_RESPONSE_MODE_FRAGMENT) == 0))) {
2046 		return oidc_util_html_send_error(r, c->error_template,
2047 				"Invalid Request",
2048 				"You've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you should not open this URL in your browser directly, or have the server administrator use a different " OIDCRedirectURI " setting.",
2049 				HTTP_INTERNAL_SERVER_ERROR);
2050 	}
2051 
2052 	/* get the parameters */
2053 	response_mode = (char*) apr_table_get(params, OIDC_PROTO_RESPONSE_MODE);
2054 
2055 	/* do the actual implicit work */
2056 	return oidc_handle_authorization_response(r, c, session, params,
2057 			response_mode ? response_mode : OIDC_PROTO_RESPONSE_MODE_FORM_POST);
2058 }
2059 
2060 /*
2061  * handle an OpenID Connect Authorization Response using the redirect response_mode
2062  */
oidc_handle_redirect_authorization_response(request_rec * r,oidc_cfg * c,oidc_session_t * session)2063 static int oidc_handle_redirect_authorization_response(request_rec *r,
2064 		oidc_cfg *c, oidc_session_t *session) {
2065 
2066 	oidc_debug(r, "enter");
2067 
2068 	/* read the parameters from the query string */
2069 	apr_table_t *params = apr_table_make(r->pool, 8);
2070 	oidc_util_read_form_encoded_params(r, params, r->args);
2071 
2072 	/* do the actual work */
2073 	return oidc_handle_authorization_response(r, c, session, params,
2074 			OIDC_PROTO_RESPONSE_MODE_QUERY);
2075 }
2076 
2077 /*
2078  * present the user with an OP selection screen
2079  */
oidc_discovery(request_rec * r,oidc_cfg * cfg)2080 static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
2081 
2082 	oidc_debug(r, "enter");
2083 
2084 	/* obtain the URL we're currently accessing, to be stored in the state/session */
2085 	char *current_url = oidc_get_current_url(r);
2086 	const char *method = oidc_original_request_method(r, cfg, FALSE);
2087 
2088 	/* generate CSRF token */
2089 	char *csrf = NULL;
2090 	if (oidc_proto_generate_nonce(r, &csrf, 8) == FALSE)
2091 		return HTTP_INTERNAL_SERVER_ERROR;
2092 
2093 	char *path_scopes = oidc_dir_cfg_path_scope(r);
2094 	char *path_auth_request_params = oidc_dir_cfg_path_auth_request_params(r);
2095 
2096 	char *discover_url = oidc_cfg_dir_discover_url(r);
2097 	/* see if there's an external discovery page configured */
2098 	if (discover_url != NULL) {
2099 
2100 		/* yes, assemble the parameters for external discovery */
2101 		char *url = apr_psprintf(r->pool, "%s%s%s=%s&%s=%s&%s=%s&%s=%s",
2102 				discover_url,
2103 				strchr(discover_url, OIDC_CHAR_QUERY) != NULL ?
2104 						OIDC_STR_AMP :
2105 						OIDC_STR_QUERY,
2106 						OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url),
2107 						OIDC_DISC_RM_PARAM, method,
2108 						OIDC_DISC_CB_PARAM,
2109 						oidc_util_escape_string(r, oidc_get_redirect_uri(r, cfg)),
2110 						OIDC_CSRF_NAME, oidc_util_escape_string(r, csrf));
2111 
2112 		if (path_scopes != NULL)
2113 			url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_SC_PARAM,
2114 					oidc_util_escape_string(r, path_scopes));
2115 		if (path_auth_request_params != NULL)
2116 			url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_AR_PARAM,
2117 					oidc_util_escape_string(r, path_auth_request_params));
2118 
2119 		/* log what we're about to do */
2120 		oidc_debug(r, "redirecting to external discovery page: %s", url);
2121 
2122 		/* set CSRF cookie */
2123 		oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
2124 				OIDC_COOKIE_SAMESITE_STRICT(cfg, r));
2125 
2126 		/* see if we need to preserve POST parameters through Javascript/HTML5 storage */
2127 		if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE)
2128 			return OK;
2129 
2130 		/* do the actual redirect to an external discovery page */
2131 		oidc_util_hdr_out_location_set(r, url);
2132 
2133 		return HTTP_MOVED_TEMPORARILY;
2134 	}
2135 
2136 	/* get a list of all providers configured in the metadata directory */
2137 	apr_array_header_t *arr = NULL;
2138 	if (oidc_metadata_list(r, cfg, &arr) == FALSE)
2139 		return oidc_util_html_send_error(r, cfg->error_template,
2140 				"Configuration Error",
2141 				"No configured providers found, contact your administrator",
2142 				HTTP_UNAUTHORIZED);
2143 
2144 	/* assemble a where-are-you-from IDP discovery HTML page */
2145 	const char *s = "			<h3>Select your OpenID Connect Identity Provider</h3>\n";
2146 
2147 	/* list all configured providers in there */
2148 	int i;
2149 	for (i = 0; i < arr->nelts; i++) {
2150 
2151 		const char *issuer = ((const char**) arr->elts)[i];
2152 		// TODO: html escape (especially & character)
2153 
2154 		char *href = apr_psprintf(r->pool,
2155 				"%s?%s=%s&amp;%s=%s&amp;%s=%s&amp;%s=%s",
2156 				oidc_get_redirect_uri(r, cfg), OIDC_DISC_OP_PARAM,
2157 				oidc_util_escape_string(r, issuer),
2158 				OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url),
2159 				OIDC_DISC_RM_PARAM, method,
2160 				OIDC_CSRF_NAME, csrf);
2161 
2162 		if (path_scopes != NULL)
2163 			href = apr_psprintf(r->pool, "%s&amp;%s=%s", href,
2164 					OIDC_DISC_SC_PARAM, oidc_util_escape_string(r, path_scopes));
2165 		if (path_auth_request_params != NULL)
2166 			href = apr_psprintf(r->pool, "%s&amp;%s=%s", href,
2167 					OIDC_DISC_AR_PARAM,
2168 					oidc_util_escape_string(r, path_auth_request_params));
2169 
2170 		char *display =
2171 				(strstr(issuer, "https://") == NULL) ?
2172 						apr_pstrdup(r->pool, issuer) :
2173 						apr_pstrdup(r->pool, issuer + strlen("https://"));
2174 
2175 		/* strip port number */
2176 		//char *p = strstr(display, ":");
2177 		//if (p != NULL) *p = '\0';
2178 		/* point back to the redirect_uri, where the selection is handled, with an IDP selection and return_to URL */
2179 		s = apr_psprintf(r->pool, "%s<p><a href=\"%s\">%s</a></p>\n", s, href,
2180 				display);
2181 	}
2182 
2183 	/* add an option to enter an account or issuer name for dynamic OP discovery */
2184 	s = apr_psprintf(r->pool, "%s<form method=\"get\" action=\"%s\">\n", s,
2185 			oidc_get_redirect_uri(r, cfg));
2186 	s = apr_psprintf(r->pool,
2187 			"%s<p><input type=\"hidden\" name=\"%s\" value=\"%s\"><p>\n", s,
2188 			OIDC_DISC_RT_PARAM, current_url);
2189 	s = apr_psprintf(r->pool,
2190 			"%s<p><input type=\"hidden\" name=\"%s\" value=\"%s\"><p>\n", s,
2191 			OIDC_DISC_RM_PARAM, method);
2192 	s = apr_psprintf(r->pool,
2193 			"%s<p><input type=\"hidden\" name=\"%s\" value=\"%s\"><p>\n", s,
2194 			OIDC_CSRF_NAME, csrf);
2195 
2196 	if (path_scopes != NULL)
2197 		s = apr_psprintf(r->pool,
2198 				"%s<p><input type=\"hidden\" name=\"%s\" value=\"%s\"><p>\n", s,
2199 				OIDC_DISC_SC_PARAM, path_scopes);
2200 	if (path_auth_request_params != NULL)
2201 		s = apr_psprintf(r->pool,
2202 				"%s<p><input type=\"hidden\" name=\"%s\" value=\"%s\"><p>\n", s,
2203 				OIDC_DISC_AR_PARAM, path_auth_request_params);
2204 
2205 	s =
2206 			apr_psprintf(r->pool,
2207 					"%s<p>Or enter your account name (eg. &quot;mike@seed.gluu.org&quot;, or an IDP identifier (eg. &quot;mitreid.org&quot;):</p>\n",
2208 					s);
2209 	s = apr_psprintf(r->pool,
2210 			"%s<p><input type=\"text\" name=\"%s\" value=\"%s\"></p>\n", s,
2211 			OIDC_DISC_OP_PARAM, "");
2212 	s = apr_psprintf(r->pool,
2213 			"%s<p><input type=\"submit\" value=\"Submit\"></p>\n", s);
2214 	s = apr_psprintf(r->pool, "%s</form>\n", s);
2215 
2216 	oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
2217 			OIDC_COOKIE_SAMESITE_STRICT(cfg, r));
2218 
2219 	char *javascript = NULL, *javascript_method = NULL;
2220 	char *html_head =
2221 			"<style type=\"text/css\">body {text-align: center}</style>";
2222 	if (oidc_post_preserve_javascript(r, NULL, &javascript, &javascript_method)
2223 			== TRUE)
2224 		html_head = apr_psprintf(r->pool, "%s%s", html_head, javascript);
2225 
2226 	/* now send the HTML contents to the user agent */
2227 	return oidc_util_html_send(r, "OpenID Connect Provider Discovery",
2228 			html_head, javascript_method, s, OK);
2229 }
2230 
2231 /*
2232  * authenticate the user to the selected OP, if the OP is not selected yet perform discovery first
2233  */
oidc_authenticate_user(request_rec * r,oidc_cfg * c,oidc_provider_t * provider,const char * original_url,const char * login_hint,const char * id_token_hint,const char * prompt,const char * auth_request_params,const char * path_scope)2234 static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
2235 		oidc_provider_t *provider, const char *original_url,
2236 		const char *login_hint, const char *id_token_hint, const char *prompt,
2237 		const char *auth_request_params, const char *path_scope) {
2238 
2239 	oidc_debug(r, "enter");
2240 
2241 	if (provider == NULL) {
2242 
2243 		// TODO: should we use an explicit redirect to the discovery endpoint (maybe a "discovery" param to the redirect_uri)?
2244 		if (c->metadata_dir != NULL)
2245 			return oidc_discovery(r, c);
2246 
2247 		/* we're not using multiple OP's configured in a metadata directory, pick the statically configured OP */
2248 		if (oidc_provider_static_config(r, c, &provider) == FALSE)
2249 			return HTTP_INTERNAL_SERVER_ERROR;
2250 	}
2251 
2252 	/* generate the random nonce value that correlates requests and responses */
2253 	char *nonce = NULL;
2254 	if (oidc_proto_generate_nonce(r, &nonce, OIDC_PROTO_NONCE_LENGTH) == FALSE)
2255 		return HTTP_INTERNAL_SERVER_ERROR;
2256 
2257 	char *pkce_state = NULL;
2258 	char *code_challenge = NULL;
2259 
2260 	if ((oidc_util_spaced_string_contains(r->pool, provider->response_type,
2261 			OIDC_PROTO_CODE) == TRUE) && (provider->pkce != NULL)) {
2262 
2263 		/* generate the code verifier value that correlates authorization requests and code exchange requests */
2264 		if (provider->pkce->state(r, &pkce_state) == FALSE)
2265 			return HTTP_INTERNAL_SERVER_ERROR;
2266 
2267 		/* generate the PKCE code challenge */
2268 		if (provider->pkce->challenge(r, pkce_state, &code_challenge) == FALSE)
2269 			return HTTP_INTERNAL_SERVER_ERROR;
2270 	}
2271 
2272 	/* create the state between request/response */
2273 	oidc_proto_state_t *proto_state = oidc_proto_state_new();
2274 	oidc_proto_state_set_original_url(proto_state, original_url);
2275 	oidc_proto_state_set_original_method(proto_state,
2276 			oidc_original_request_method(r, c, TRUE));
2277 	oidc_proto_state_set_issuer(proto_state, provider->issuer);
2278 	oidc_proto_state_set_response_type(proto_state, provider->response_type);
2279 	oidc_proto_state_set_nonce(proto_state, nonce);
2280 	oidc_proto_state_set_timestamp_now(proto_state);
2281 	if (provider->response_mode)
2282 		oidc_proto_state_set_response_mode(proto_state,
2283 				provider->response_mode);
2284 	if (prompt)
2285 		oidc_proto_state_set_prompt(proto_state, prompt);
2286 	if (pkce_state)
2287 		oidc_proto_state_set_pkce_state(proto_state, pkce_state);
2288 
2289 	/* get a hash value that fingerprints the browser concatenated with the random input */
2290 	char *state = oidc_get_browser_state_hash(r, c, nonce);
2291 
2292 	/*
2293 	 * create state that restores the context when the authorization response comes in
2294 	 * and cryptographically bind it to the browser
2295 	 */
2296 	int rc = oidc_authorization_request_set_cookie(r, c, state, proto_state);
2297 	if (rc != OK) {
2298 		oidc_proto_state_destroy(proto_state);
2299 		return rc;
2300 	}
2301 
2302 	/*
2303 	 * printout errors if Cookie settings are not going to work
2304 	 * TODO: separate this code out into its own function
2305 	 */
2306 	apr_uri_t o_uri;
2307 	memset(&o_uri, 0, sizeof(apr_uri_t));
2308 	apr_uri_t r_uri;
2309 	memset(&r_uri, 0, sizeof(apr_uri_t));
2310 	apr_uri_parse(r->pool, original_url, &o_uri);
2311 	apr_uri_parse(r->pool, oidc_get_redirect_uri(r, c), &r_uri);
2312 	if ((apr_strnatcmp(o_uri.scheme, r_uri.scheme) != 0)
2313 			&& (apr_strnatcmp(r_uri.scheme, "https") == 0)) {
2314 		oidc_error(r,
2315 				"the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
2316 				r_uri.scheme, o_uri.scheme);
2317 		oidc_proto_state_destroy(proto_state);
2318 		return HTTP_INTERNAL_SERVER_ERROR;
2319 	}
2320 
2321 	if (c->cookie_domain == NULL) {
2322 		if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) {
2323 			char *p = strstr(o_uri.hostname, r_uri.hostname);
2324 			if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
2325 				oidc_error(r,
2326 						"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
2327 						r_uri.hostname, o_uri.hostname);
2328 				oidc_proto_state_destroy(proto_state);
2329 				return HTTP_INTERNAL_SERVER_ERROR;
2330 			}
2331 		}
2332 	} else {
2333 		if (!oidc_util_cookie_domain_valid(r_uri.hostname, c->cookie_domain)) {
2334 			oidc_error(r,
2335 					"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!",
2336 					c->cookie_domain, o_uri.hostname, original_url);
2337 			oidc_proto_state_destroy(proto_state);
2338 			return HTTP_INTERNAL_SERVER_ERROR;
2339 		}
2340 	}
2341 
2342 	/* send off to the OpenID Connect Provider */
2343 	// TODO: maybe show intermediate/progress screen "redirecting to"
2344 	return oidc_proto_authorization_request(r, provider, login_hint,
2345 			oidc_get_redirect_uri_iss(r, c, provider), state, proto_state,
2346 			id_token_hint, code_challenge, auth_request_params, path_scope);
2347 }
2348 
2349 /*
2350  * check if the target_link_uri matches to configuration settings to prevent an open redirect
2351  */
oidc_target_link_uri_matches_configuration(request_rec * r,oidc_cfg * cfg,const char * target_link_uri)2352 static int oidc_target_link_uri_matches_configuration(request_rec *r,
2353 		oidc_cfg *cfg, const char *target_link_uri) {
2354 
2355 	apr_uri_t o_uri;
2356 	apr_uri_parse(r->pool, target_link_uri, &o_uri);
2357 	if (o_uri.hostname == NULL) {
2358 		oidc_error(r,
2359 				"could not parse the \"target_link_uri\" (%s) in to a valid URL: aborting.",
2360 				target_link_uri);
2361 		return FALSE;
2362 	}
2363 
2364 	apr_uri_t r_uri;
2365 	apr_uri_parse(r->pool, oidc_get_redirect_uri(r, cfg), &r_uri);
2366 
2367 	if (cfg->cookie_domain == NULL) {
2368 		/* cookie_domain set: see if the target_link_uri matches the redirect_uri host (because the session cookie will be set host-wide) */
2369 		if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) {
2370 			char *p = strstr(o_uri.hostname, r_uri.hostname);
2371 			if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
2372 				oidc_error(r,
2373 						"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
2374 						r_uri.hostname, o_uri.hostname);
2375 				return FALSE;
2376 			}
2377 		}
2378 	} else {
2379 		/* cookie_domain set: see if the target_link_uri is within the cookie_domain */
2380 		char *p = strstr(o_uri.hostname, cfg->cookie_domain);
2381 		if ((p == NULL) || (apr_strnatcmp(cfg->cookie_domain, p) != 0)) {
2382 			oidc_error(r,
2383 					"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
2384 					cfg->cookie_domain, o_uri.hostname, target_link_uri);
2385 			return FALSE;
2386 		}
2387 	}
2388 
2389 	/* see if the cookie_path setting matches the target_link_uri path */
2390 	char *cookie_path = oidc_cfg_dir_cookie_path(r);
2391 	if (cookie_path != NULL) {
2392 		char *p = (o_uri.path != NULL) ? strstr(o_uri.path, cookie_path) : NULL;
2393 		if ((p == NULL) || (p != o_uri.path)) {
2394 			oidc_error(r,
2395 					"the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
2396 					cfg->cookie_domain, o_uri.path, target_link_uri);
2397 			return FALSE;
2398 		} else if (strlen(o_uri.path) > strlen(cookie_path)) {
2399 			int n = strlen(cookie_path);
2400 			if (cookie_path[n - 1] == OIDC_CHAR_FORWARD_SLASH)
2401 				n--;
2402 			if (o_uri.path[n] != OIDC_CHAR_FORWARD_SLASH) {
2403 				oidc_error(r,
2404 						"the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
2405 						cfg->cookie_domain, o_uri.path, target_link_uri);
2406 				return FALSE;
2407 			}
2408 		}
2409 	}
2410 	return TRUE;
2411 }
2412 
2413 /*
2414  * handle a response from an IDP discovery page and/or handle 3rd-party initiated SSO
2415  */
oidc_handle_discovery_response(request_rec * r,oidc_cfg * c)2416 static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
2417 
2418 	/* variables to hold the values returned in the response */
2419 	char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL,
2420 			*auth_request_params = NULL, *csrf_cookie, *csrf_query = NULL,
2421 			*user = NULL, *path_scopes;
2422 	oidc_provider_t *provider = NULL;
2423 
2424 	oidc_util_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer);
2425 	oidc_util_get_request_parameter(r, OIDC_DISC_USER_PARAM, &user);
2426 	oidc_util_get_request_parameter(r, OIDC_DISC_RT_PARAM, &target_link_uri);
2427 	oidc_util_get_request_parameter(r, OIDC_DISC_LH_PARAM, &login_hint);
2428 	oidc_util_get_request_parameter(r, OIDC_DISC_SC_PARAM, &path_scopes);
2429 	oidc_util_get_request_parameter(r, OIDC_DISC_AR_PARAM,
2430 			&auth_request_params);
2431 	oidc_util_get_request_parameter(r, OIDC_CSRF_NAME, &csrf_query);
2432 	csrf_cookie = oidc_util_get_cookie(r, OIDC_CSRF_NAME);
2433 
2434 	/* do CSRF protection if not 3rd party initiated SSO */
2435 	if (csrf_cookie) {
2436 
2437 		/* clean CSRF cookie */
2438 		oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0,
2439 				OIDC_COOKIE_EXT_SAME_SITE_NONE(r));
2440 
2441 		/* compare CSRF cookie value with query parameter value */
2442 		if ((csrf_query == NULL)
2443 				|| apr_strnatcmp(csrf_query, csrf_cookie) != 0) {
2444 			oidc_warn(r,
2445 					"CSRF protection failed, no Discovery and dynamic client registration will be allowed");
2446 			csrf_cookie = NULL;
2447 		}
2448 	}
2449 
2450 	// TODO: trim issuer/accountname/domain input and do more input validation
2451 
2452 	oidc_debug(r,
2453 			"issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\", user=\"%s\"",
2454 			issuer, target_link_uri, login_hint, user);
2455 
2456 	if (target_link_uri == NULL) {
2457 		if (c->default_sso_url == NULL) {
2458 			return oidc_util_html_send_error(r, c->error_template,
2459 					"Invalid Request",
2460 					"SSO to this module without specifying a \"target_link_uri\" parameter is not possible because " OIDCDefaultURL " is not set.",
2461 					HTTP_INTERNAL_SERVER_ERROR);
2462 		}
2463 		target_link_uri = c->default_sso_url;
2464 	}
2465 
2466 	/* do open redirect prevention */
2467 	if (oidc_target_link_uri_matches_configuration(r, c, target_link_uri)
2468 			== FALSE) {
2469 		return oidc_util_html_send_error(r, c->error_template,
2470 				"Invalid Request",
2471 				"\"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.",
2472 				HTTP_UNAUTHORIZED);
2473 	}
2474 
2475 	/* see if this is a static setup */
2476 	if (c->metadata_dir == NULL) {
2477 		if ((oidc_provider_static_config(r, c, &provider) == TRUE)
2478 				&& (issuer != NULL)) {
2479 			if (apr_strnatcmp(provider->issuer, issuer) != 0) {
2480 				return oidc_util_html_send_error(r, c->error_template,
2481 						"Invalid Request",
2482 						apr_psprintf(r->pool,
2483 								"The \"iss\" value must match the configured providers' one (%s != %s).",
2484 								issuer, c->provider.issuer),
2485 								HTTP_INTERNAL_SERVER_ERROR);
2486 			}
2487 		}
2488 		return oidc_authenticate_user(r, c, NULL, target_link_uri, login_hint,
2489 				NULL, NULL, auth_request_params, path_scopes);
2490 	}
2491 
2492 	/* find out if the user entered an account name or selected an OP manually */
2493 	if (user != NULL) {
2494 
2495 		if (login_hint == NULL)
2496 			login_hint = apr_pstrdup(r->pool, user);
2497 
2498 		/* normalize the user identifier */
2499 		if (strstr(user, "https://") != user)
2500 			user = apr_psprintf(r->pool, "https://%s", user);
2501 
2502 		/* got an user identifier as input, perform OP discovery with that */
2503 		if (oidc_proto_url_based_discovery(r, c, user, &issuer) == FALSE) {
2504 
2505 			/* something did not work out, show a user facing error */
2506 			return oidc_util_html_send_error(r, c->error_template,
2507 					"Invalid Request",
2508 					"Could not resolve the provided user identifier to an OpenID Connect provider; check your syntax.",
2509 					HTTP_NOT_FOUND);
2510 		}
2511 
2512 		/* issuer is set now, so let's continue as planned */
2513 
2514 	} else if (strstr(issuer, OIDC_STR_AT) != NULL) {
2515 
2516 		if (login_hint == NULL) {
2517 			login_hint = apr_pstrdup(r->pool, issuer);
2518 			//char *p = strstr(issuer, OIDC_STR_AT);
2519 			//*p = '\0';
2520 		}
2521 
2522 		/* got an account name as input, perform OP discovery with that */
2523 		if (oidc_proto_account_based_discovery(r, c, issuer, &issuer)
2524 				== FALSE) {
2525 
2526 			/* something did not work out, show a user facing error */
2527 			return oidc_util_html_send_error(r, c->error_template,
2528 					"Invalid Request",
2529 					"Could not resolve the provided account name to an OpenID Connect provider; check your syntax.",
2530 					HTTP_NOT_FOUND);
2531 		}
2532 
2533 		/* issuer is set now, so let's continue as planned */
2534 
2535 	}
2536 
2537 	/* strip trailing '/' */
2538 	int n = strlen(issuer);
2539 	if (issuer[n - 1] == OIDC_CHAR_FORWARD_SLASH)
2540 		issuer[n - 1] = '\0';
2541 
2542 	/* try and get metadata from the metadata directories for the selected OP */
2543 	if ((oidc_metadata_get(r, c, issuer, &provider, csrf_cookie != NULL) == TRUE)
2544 			&& (provider != NULL)) {
2545 
2546 		/* now we've got a selected OP, send the user there to authenticate */
2547 		return oidc_authenticate_user(r, c, provider, target_link_uri,
2548 				login_hint, NULL, NULL, auth_request_params, path_scopes);
2549 	}
2550 
2551 	/* something went wrong */
2552 	return oidc_util_html_send_error(r, c->error_template, "Invalid Request",
2553 			"Could not find valid provider metadata for the selected OpenID Connect provider; contact the administrator",
2554 			HTTP_NOT_FOUND);
2555 }
2556 
2557 static apr_uint32_t oidc_transparent_pixel[17] = { 0x474e5089, 0x0a1a0a0d,
2558 		0x0d000000, 0x52444849, 0x01000000, 0x01000000, 0x00000408, 0x0c1cb500,
2559 		0x00000002, 0x4144490b, 0x639c7854, 0x0000cffa, 0x02010702, 0x71311c9a,
2560 		0x00000000, 0x444e4549, 0x826042ae };
2561 
oidc_is_front_channel_logout(const char * logout_param_value)2562 static apr_byte_t oidc_is_front_channel_logout(const char *logout_param_value) {
2563 	return ((logout_param_value != NULL)
2564 			&& ((apr_strnatcmp(logout_param_value,
2565 					OIDC_GET_STYLE_LOGOUT_PARAM_VALUE) == 0)
2566 					|| (apr_strnatcmp(logout_param_value,
2567 							OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0)));
2568 }
2569 
oidc_is_back_channel_logout(const char * logout_param_value)2570 static apr_byte_t oidc_is_back_channel_logout(const char *logout_param_value) {
2571 	return ((logout_param_value != NULL) && (apr_strnatcmp(logout_param_value,
2572 			OIDC_BACKCHANNEL_STYLE_LOGOUT_PARAM_VALUE) == 0));
2573 }
2574 
2575 /*
2576  * revoke refresh token and access token stored in the session if the
2577  * OP has an RFC 7009 compliant token revocation endpoint
2578  */
oidc_revoke_tokens(request_rec * r,oidc_cfg * c,oidc_session_t * session)2579 static void oidc_revoke_tokens(request_rec *r, oidc_cfg *c,
2580 		oidc_session_t *session) {
2581 
2582 	char *response = NULL;
2583 	char *basic_auth = NULL;
2584 	char *bearer_auth = NULL;
2585 	apr_table_t *params = NULL;
2586 	const char *token = NULL;
2587 	oidc_provider_t *provider = NULL;
2588 
2589 	oidc_debug(r, "enter");
2590 
2591 	if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE)
2592 		goto out;
2593 
2594 	oidc_debug(r, "revocation_endpoint=%s",
2595 			provider->revocation_endpoint_url ?
2596 					provider->revocation_endpoint_url : "(null)");
2597 
2598 	if (provider->revocation_endpoint_url == NULL)
2599 		goto out;
2600 
2601 	params = apr_table_make(r->pool, 4);
2602 
2603 	// add the token endpoint authentication credentials to the revocation endpoint call...
2604 	if (oidc_proto_token_endpoint_auth(r, c, provider->token_endpoint_auth,
2605 			provider->client_id, provider->client_secret,
2606 			provider->client_signing_keys, provider->token_endpoint_url, params,
2607 			NULL, &basic_auth, &bearer_auth) == FALSE)
2608 		goto out;
2609 
2610 	// TODO: use oauth.ssl_validate_server ...
2611 	token = oidc_session_get_refresh_token(r, session);
2612 	if (token != NULL) {
2613 		apr_table_addn(params, "token_type_hint", "refresh_token");
2614 		apr_table_addn(params, "token", token);
2615 
2616 		if (oidc_util_http_post_form(r, provider->revocation_endpoint_url,
2617 				params, basic_auth, bearer_auth, c->oauth.ssl_validate_server,
2618 				&response, c->http_timeout_long, c->outgoing_proxy,
2619 				oidc_dir_cfg_pass_cookies(r), NULL,
2620 				NULL) == FALSE) {
2621 			oidc_warn(r, "revoking refresh token failed");
2622 		}
2623 		apr_table_clear(params);
2624 	}
2625 
2626 	token = oidc_session_get_access_token(r, session);
2627 	if (token != NULL) {
2628 		apr_table_addn(params, "token_type_hint", "access_token");
2629 		apr_table_addn(params, "token", token);
2630 
2631 		if (oidc_util_http_post_form(r, provider->revocation_endpoint_url,
2632 				params, basic_auth, bearer_auth, c->oauth.ssl_validate_server,
2633 				&response, c->http_timeout_long, c->outgoing_proxy,
2634 				oidc_dir_cfg_pass_cookies(r), NULL,
2635 				NULL) == FALSE) {
2636 			oidc_warn(r, "revoking access token failed");
2637 		}
2638 	}
2639 
2640 out:
2641 
2642 	oidc_debug(r, "leave");
2643 }
2644 
2645 /*
2646  * handle a local logout
2647  */
oidc_handle_logout_request(request_rec * r,oidc_cfg * c,oidc_session_t * session,const char * url)2648 static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
2649 		oidc_session_t *session, const char *url) {
2650 
2651 	oidc_debug(r, "enter (url=%s)", url);
2652 
2653 	/* if there's no remote_user then there's no (stored) session to kill */
2654 	if (session->remote_user != NULL)
2655 		oidc_revoke_tokens(r, c, session);
2656 
2657 	/*
2658 	 * remove session state (cq. cache entry and cookie)
2659 	 * always clear the session cookie because the cookie may be not sent (but still in the browser)
2660 	 * due to SameSite policies
2661 	 */
2662 	oidc_session_kill(r, session);
2663 
2664 	/* see if this is the OP calling us */
2665 	if (oidc_is_front_channel_logout(url)) {
2666 
2667 		/* set recommended cache control headers */
2668 		oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL,
2669 				"no-cache, no-store");
2670 		oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache");
2671 		oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_P3P, "CAO PSA OUR");
2672 		oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_EXPIRES, "0");
2673 		oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_X_FRAME_OPTIONS, "DENY");
2674 
2675 		/* see if this is PF-PA style logout in which case we return a transparent pixel */
2676 		const char *accept = oidc_util_hdr_in_accept_get(r);
2677 		if ((apr_strnatcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0)
2678 				|| ((accept) && strstr(accept, OIDC_CONTENT_TYPE_IMAGE_PNG))) {
2679 			// terminate with DONE instead of OK
2680 			// to avoid Apache returning auth/authz error 401 for the redirect URI
2681 			return oidc_util_http_send(r, (const char*) &oidc_transparent_pixel,
2682 					sizeof(oidc_transparent_pixel), OIDC_CONTENT_TYPE_IMAGE_PNG,
2683 					DONE);
2684 		}
2685 
2686 		/* standard HTTP based logout: should be called in an iframe from the OP */
2687 		return oidc_util_html_send(r, "Logged Out", NULL, NULL,
2688 				"<p>Logged Out</p>", DONE);
2689 	}
2690 
2691 	/* see if we don't need to go somewhere special after killing the session locally */
2692 	if (url == NULL)
2693 		return oidc_util_html_send(r, "Logged Out", NULL, NULL,
2694 				"<p>Logged Out</p>", OK);
2695 
2696 	/* send the user to the specified where-to-go-after-logout URL */
2697 	oidc_util_hdr_out_location_set(r, url);
2698 
2699 	return HTTP_MOVED_TEMPORARILY;
2700 }
2701 
2702 /*
2703  * handle a backchannel logout
2704  */
2705 #define OIDC_EVENTS_BLOGOUT_KEY "http://schemas.openid.net/event/backchannel-logout"
2706 
oidc_handle_logout_backchannel(request_rec * r,oidc_cfg * cfg)2707 static int oidc_handle_logout_backchannel(request_rec *r, oidc_cfg *cfg) {
2708 
2709 	oidc_debug(r, "enter");
2710 
2711 	const char *logout_token = NULL;
2712 	oidc_jwt_t *jwt = NULL;
2713 	oidc_jose_error_t err;
2714 	oidc_jwk_t *jwk = NULL;
2715 	oidc_provider_t *provider = NULL;
2716 	char *sid = NULL, *uuid = NULL;
2717 	oidc_session_t session;
2718 	int rc = HTTP_BAD_REQUEST;
2719 
2720 	apr_table_t *params = apr_table_make(r->pool, 8);
2721 	if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) {
2722 		oidc_error(r,
2723 				"could not read POST-ed parameters to the logout endpoint");
2724 		goto out;
2725 	}
2726 
2727 	logout_token = apr_table_get(params, OIDC_PROTO_LOGOUT_TOKEN);
2728 	if (logout_token == NULL) {
2729 		oidc_error(r,
2730 				"backchannel lggout endpoint was called but could not find a parameter named \"%s\"",
2731 				OIDC_PROTO_LOGOUT_TOKEN);
2732 		goto out;
2733 	}
2734 
2735 	// TODO: jwk symmetric key based on provider
2736 
2737 	if (oidc_jwt_parse(r->pool, logout_token, &jwt,
2738 			oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, NULL),
2739 			&err) == FALSE) {
2740 		oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err));
2741 		goto out;
2742 	}
2743 
2744 	provider = oidc_get_provider_for_issuer(r, cfg, jwt->payload.iss, FALSE);
2745 	if (provider == NULL) {
2746 		oidc_error(r, "no provider found for issuer: %s", jwt->payload.iss);
2747 		goto out;
2748 	}
2749 
2750 	// TODO: destroy the JWK used for decryption
2751 
2752 	jwk = NULL;
2753 	if (oidc_util_create_symmetric_key(r, provider->client_secret, 0,
2754 			NULL, TRUE, &jwk) == FALSE)
2755 		return FALSE;
2756 
2757 	oidc_jwks_uri_t jwks_uri = { provider->jwks_uri,
2758 			provider->jwks_refresh_interval, provider->ssl_validate_server };
2759 	if (oidc_proto_jwt_verify(r, cfg, jwt, &jwks_uri,
2760 			oidc_util_merge_symmetric_key(r->pool, NULL, jwk),
2761 			provider->id_token_signed_response_alg) == FALSE) {
2762 
2763 		oidc_error(r, "id_token signature could not be validated, aborting");
2764 		goto out;
2765 	}
2766 
2767 	// oidc_proto_validate_idtoken would try and require a token binding cnf
2768 	// if the policy is set to "required", so don't use that here
2769 	if (oidc_proto_validate_jwt(r, jwt,
2770 			provider->validate_issuer ? provider->issuer : NULL, FALSE, FALSE,
2771 					provider->idtoken_iat_slack,
2772 					OIDC_TOKEN_BINDING_POLICY_DISABLED) == FALSE)
2773 		goto out;
2774 
2775 	/* verify the "aud" and "azp" values */
2776 	if (oidc_proto_validate_aud_and_azp(r, cfg, provider, &jwt->payload)
2777 			== FALSE)
2778 		goto out;
2779 
2780 	json_t *events = json_object_get(jwt->payload.value.json,
2781 			OIDC_CLAIM_EVENTS);
2782 	if (events == NULL) {
2783 		oidc_error(r, "\"%s\" claim could not be found in logout token",
2784 				OIDC_CLAIM_EVENTS);
2785 		goto out;
2786 	}
2787 
2788 	json_t *blogout = json_object_get(events, OIDC_EVENTS_BLOGOUT_KEY);
2789 	if (!json_is_object(blogout)) {
2790 		oidc_error(r, "\"%s\" object could not be found in \"%s\" claim",
2791 				OIDC_EVENTS_BLOGOUT_KEY, OIDC_CLAIM_EVENTS);
2792 		goto out;
2793 	}
2794 
2795 	char *nonce = NULL;
2796 	oidc_json_object_get_string(r->pool, jwt->payload.value.json,
2797 			OIDC_CLAIM_NONCE, &nonce, NULL);
2798 	if (nonce != NULL) {
2799 		oidc_error(r,
2800 				"rejecting logout request/token since it contains a \"%s\" claim",
2801 				OIDC_CLAIM_NONCE);
2802 		goto out;
2803 	}
2804 
2805 	char *jti = NULL;
2806 	oidc_json_object_get_string(r->pool, jwt->payload.value.json,
2807 			OIDC_CLAIM_JTI, &jti, NULL);
2808 	if (jti != NULL) {
2809 		char *replay = NULL;
2810 		oidc_cache_get_jti(r, jti, &replay);
2811 		if (replay != NULL) {
2812 			oidc_error(r,
2813 					"the \"%s\" value (%s) passed in logout token was found in the cache already; possible replay attack!?",
2814 					OIDC_CLAIM_JTI, jti);
2815 			goto out;
2816 		}
2817 	}
2818 
2819 	/* jti cache duration is the configured replay prevention window for token issuance plus 10 seconds for safety */
2820 	apr_time_t jti_cache_duration = apr_time_from_sec(
2821 			provider->idtoken_iat_slack * 2 + 10);
2822 
2823 	/* store it in the cache for the calculated duration */
2824 	oidc_cache_set_jti(r, jti, jti, apr_time_now() + jti_cache_duration);
2825 
2826 	oidc_json_object_get_string(r->pool, jwt->payload.value.json,
2827 			OIDC_CLAIM_EVENTS, &sid, NULL);
2828 
2829 	// TODO: by-spec we should cater for the fact that "sid" has been provided
2830 	//       in the id_token returned in the authentication request, but "sub"
2831 	//       is used in the logout token but that requires a 2nd entry in the
2832 	//       cache and a separate session "sub" member, ugh; we'll just assume
2833 	//       that is "sid" is specified in the id_token, the OP will actually use
2834 	//       this for logout
2835 	//       (and probably call us multiple times or the same sub if needed)
2836 
2837 	oidc_json_object_get_string(r->pool, jwt->payload.value.json,
2838 			OIDC_CLAIM_SID, &sid, NULL);
2839 	if (sid == NULL)
2840 		sid = jwt->payload.sub;
2841 
2842 	if (sid == NULL) {
2843 		oidc_error(r, "no \"sub\" and no \"sid\" claim found in logout token");
2844 		goto out;
2845 	}
2846 
2847 	// TODO: when dealing with sub instead of a true sid, we'll be killing all sessions for
2848 	//       a specific user, across hosts that share the *same* cache backend
2849 	//       if those hosts haven't been configured with a different OIDCCryptoPassphrase
2850 	//       - perhaps that's even acceptable since non-memory caching is encrypted by default
2851 	//         and memory-based caching doesn't suffer from this (different shm segments)?
2852 	//       - it will result in 400 errors returned from backchannel logout calls to the other hosts...
2853 
2854 	sid = oidc_make_sid_iss_unique(r, sid, provider->issuer);
2855 	oidc_cache_get_sid(r, sid, &uuid);
2856 	if (uuid == NULL) {
2857 		oidc_error(r,
2858 				"could not find session based on sid/sub provided in logout token: %s",
2859 				sid);
2860 		// return HTTP 200 according to (new?) spec and terminate early
2861 		// to avoid Apache returning auth/authz error 500 for the redirect URI
2862 		rc = DONE;
2863 		goto out;
2864 	}
2865 
2866 	// revoke tokens if we can get a handle on those
2867 	if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
2868 		if (oidc_session_load_cache_by_uuid(r, cfg, uuid, &session) != FALSE)
2869 			if (oidc_session_extract(r, &session) != FALSE)
2870 				oidc_revoke_tokens(r, cfg, &session);
2871 	}
2872 
2873 	// clear the session cache
2874 	oidc_cache_set_sid(r, sid, NULL, 0);
2875 	oidc_cache_set_session(r, uuid, NULL, 0);
2876 
2877 	// terminate with DONE instead of OK
2878 	// to avoid Apache returning auth/authz error 500 for the redirect URI
2879 	rc = DONE;
2880 
2881 out:
2882 
2883 	if (jwk != NULL) {
2884 		oidc_jwk_destroy(jwk);
2885 		jwk = NULL;
2886 
2887 	}
2888 	if (jwt != NULL) {
2889 		oidc_jwt_destroy(jwt);
2890 		jwt = NULL;
2891 	}
2892 
2893 	oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL,
2894 			"no-cache, no-store");
2895 	oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache");
2896 
2897 	return rc;
2898 }
2899 
oidc_validate_redirect_url(request_rec * r,oidc_cfg * c,const char * url,apr_byte_t restrict_to_host,char ** err_str,char ** err_desc)2900 static apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg *c,
2901 		const char *url, apr_byte_t restrict_to_host, char **err_str,
2902 		char **err_desc) {
2903 	apr_uri_t uri;
2904 	const char *c_host = NULL;
2905 	apr_hash_index_t *hi = NULL;
2906 
2907 	if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
2908 		*err_str = apr_pstrdup(r->pool, "Malformed URL");
2909 		*err_desc = apr_psprintf(r->pool, "not a valid URL value: %s", url);
2910 		oidc_error(r, "%s: %s", *err_str, *err_desc);
2911 		return FALSE;
2912 	}
2913 
2914 	if (c->redirect_urls_allowed != NULL) {
2915 		for (hi = apr_hash_first(NULL, c->redirect_urls_allowed); hi; hi =
2916 				apr_hash_next(hi)) {
2917 			apr_hash_this(hi, (const void**) &c_host, NULL, NULL);
2918 			if (oidc_util_regexp_first_match(r->pool, url, c_host,
2919 					NULL, err_str) == TRUE)
2920 				break;
2921 		}
2922 		if (hi == NULL) {
2923 			*err_str = apr_pstrdup(r->pool, "URL not allowed");
2924 			*err_desc =
2925 					apr_psprintf(r->pool,
2926 							"value does not match the list of allowed redirect URLs: %s",
2927 							url);
2928 			oidc_error(r, "%s: %s", *err_str, *err_desc);
2929 			return FALSE;
2930 		}
2931 	} else if ((uri.hostname != NULL) && (restrict_to_host == TRUE)) {
2932 		c_host = oidc_get_current_url_host(r);
2933 		if ((strstr(c_host, uri.hostname) == NULL)
2934 				|| (strstr(uri.hostname, c_host) == NULL)) {
2935 			*err_str = apr_pstrdup(r->pool, "Invalid Request");
2936 			*err_desc =
2937 					apr_psprintf(r->pool,
2938 							"URL value \"%s\" does not match the hostname of the current request \"%s\"",
2939 							apr_uri_unparse(r->pool, &uri, 0), c_host);
2940 			oidc_error(r, "%s: %s", *err_str, *err_desc);
2941 			return FALSE;
2942 		}
2943 	}
2944 
2945 	if ((uri.hostname == NULL) && (strstr(url, "/") != url)) {
2946 		*err_str = apr_pstrdup(r->pool, "Malformed URL");
2947 		*err_desc =
2948 				apr_psprintf(r->pool,
2949 						"No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s",
2950 						url);
2951 		oidc_error(r, "%s: %s", *err_str, *err_desc);
2952 		return FALSE;
2953 	} else if ((uri.hostname == NULL) && (strstr(url, "//") == url)) {
2954 		*err_str = apr_pstrdup(r->pool, "Malformed URL");
2955 		*err_desc = apr_psprintf(r->pool,
2956 				"No hostname was parsed and starting with '//': %s", url);
2957 		oidc_error(r, "%s: %s", *err_str, *err_desc);
2958 		return FALSE;
2959 	} else if ((uri.hostname == NULL) && (strstr(url, "/\\") == url)) {
2960 		*err_str = apr_pstrdup(r->pool, "Malformed URL");
2961 		*err_desc = apr_psprintf(r->pool,
2962 				"No hostname was parsed and starting with '/\\': %s", url);
2963 		oidc_error(r, "%s: %s", *err_str, *err_desc);
2964 		return FALSE;
2965 	}
2966 
2967 	/* validate the URL to prevent HTTP header splitting */
2968 	if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
2969 		*err_str = apr_pstrdup(r->pool, "Invalid URL");
2970 		*err_desc =
2971 				apr_psprintf(r->pool,
2972 						"URL value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
2973 						url);
2974 		oidc_error(r, "%s: %s", *err_str, *err_desc);
2975 		return FALSE;
2976 	}
2977 
2978 	return TRUE;
2979 }
2980 
2981 /*
2982  * perform (single) logout
2983  */
oidc_handle_logout(request_rec * r,oidc_cfg * c,oidc_session_t * session)2984 static int oidc_handle_logout(request_rec *r, oidc_cfg *c,
2985 		oidc_session_t *session) {
2986 
2987 	oidc_provider_t *provider = NULL;
2988 	/* pickup the command or URL where the user wants to go after logout */
2989 	char *url = NULL;
2990 	char *error_str = NULL;
2991 	char *error_description = NULL;
2992 
2993 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url);
2994 
2995 	oidc_debug(r, "enter (url=%s)", url);
2996 
2997 	if (oidc_is_front_channel_logout(url)) {
2998 		return oidc_handle_logout_request(r, c, session, url);
2999 	} else if (oidc_is_back_channel_logout(url)) {
3000 		return oidc_handle_logout_backchannel(r, c);
3001 	}
3002 
3003 	if ((url == NULL) || (apr_strnatcmp(url, "") == 0)) {
3004 
3005 		url = c->default_slo_url;
3006 
3007 	} else {
3008 
3009 		/* do input validation on the logout parameter value */
3010 		if (oidc_validate_redirect_url(r, c, url, TRUE, &error_str,
3011 				&error_description) == FALSE) {
3012 			return oidc_util_html_send_error(r, c->error_template, error_str,
3013 					error_description,
3014 					HTTP_BAD_REQUEST);
3015 		}
3016 	}
3017 
3018 	oidc_get_provider_from_session(r, c, session, &provider);
3019 
3020 	if ((provider != NULL) && (provider->end_session_endpoint != NULL)) {
3021 
3022 		const char *id_token_hint = oidc_session_get_idtoken(r, session);
3023 
3024 		char *logout_request = apr_pstrdup(r->pool,
3025 				provider->end_session_endpoint);
3026 		if (id_token_hint != NULL) {
3027 			logout_request = apr_psprintf(r->pool, "%s%sid_token_hint=%s",
3028 					logout_request, strchr(logout_request ? logout_request : "",
3029 							OIDC_CHAR_QUERY) != NULL ?
3030 									OIDC_STR_AMP :
3031 									OIDC_STR_QUERY,
3032 									oidc_util_escape_string(r, id_token_hint));
3033 		}
3034 
3035 		if (url != NULL) {
3036 			logout_request = apr_psprintf(r->pool,
3037 					"%s%spost_logout_redirect_uri=%s", logout_request,
3038 					strchr(logout_request ? logout_request : "",
3039 							OIDC_CHAR_QUERY) != NULL ?
3040 									OIDC_STR_AMP :
3041 									OIDC_STR_QUERY,
3042 									oidc_util_escape_string(r, url));
3043 		}
3044 		url = logout_request;
3045 	}
3046 
3047 	return oidc_handle_logout_request(r, c, session, url);
3048 }
3049 
3050 /*
3051  * handle request for JWKs
3052  */
oidc_handle_jwks(request_rec * r,oidc_cfg * c)3053 int oidc_handle_jwks(request_rec *r, oidc_cfg *c) {
3054 
3055 	/* pickup requested JWKs type */
3056 	//	char *jwks_type = NULL;
3057 	//	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS, &jwks_type);
3058 	char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : [");
3059 	int i = 0;
3060 	apr_byte_t first = TRUE;
3061 	oidc_jose_error_t err;
3062 
3063 	if (c->public_keys != NULL) {
3064 
3065 		/* loop over the RSA public keys */
3066 		for (i = 0; i < c->public_keys->nelts; i++) {
3067 			const oidc_jwk_t *jwk =
3068 					((const oidc_jwk_t**) c->public_keys->elts)[i];
3069 			char *s_json = NULL;
3070 
3071 			if (oidc_jwk_to_json(r->pool, jwk, &s_json, &err) == TRUE) {
3072 				jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",",
3073 						s_json);
3074 				first = FALSE;
3075 			} else {
3076 				oidc_error(r,
3077 						"could not convert RSA JWK to JSON using oidc_jwk_to_json: %s",
3078 						oidc_jose_e2s(r->pool, err));
3079 			}
3080 		}
3081 	}
3082 
3083 	// TODO: send stuff if first == FALSE?
3084 	jwks = apr_psprintf(r->pool, "%s ] }", jwks);
3085 
3086 	return oidc_util_http_send(r, jwks, strlen(jwks), OIDC_CONTENT_TYPE_JSON,
3087 			OK);
3088 }
3089 
oidc_handle_session_management_iframe_op(request_rec * r,oidc_cfg * c,oidc_session_t * session,const char * check_session_iframe)3090 static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c,
3091 		oidc_session_t *session, const char *check_session_iframe) {
3092 	oidc_debug(r, "enter");
3093 	oidc_util_hdr_out_location_set(r, check_session_iframe);
3094 	return HTTP_MOVED_TEMPORARILY;
3095 }
3096 
oidc_handle_session_management_iframe_rp(request_rec * r,oidc_cfg * c,oidc_session_t * session,const char * client_id,const char * check_session_iframe)3097 static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *c,
3098 		oidc_session_t *session, const char *client_id,
3099 		const char *check_session_iframe) {
3100 
3101 	oidc_debug(r, "enter");
3102 
3103 	const char *java_script =
3104 			"    <script type=\"text/javascript\">\n"
3105 			"      var targetOrigin  = '%s';\n"
3106 			"      var clientId  = '%s';\n"
3107 			"      var sessionId  = '%s';\n"
3108 			"      var loginUrl  = '%s';\n"
3109 			"      var message = clientId + ' ' + sessionId;\n"
3110 			"	   var timerID;\n"
3111 			"\n"
3112 			"      function checkSession() {\n"
3113 			"        console.debug('checkSession: posting ' + message + ' to ' + targetOrigin);\n"
3114 			"        var win = window.parent.document.getElementById('%s').contentWindow;\n"
3115 			"        win.postMessage( message, targetOrigin);\n"
3116 			"      }\n"
3117 			"\n"
3118 			"      function setTimer() {\n"
3119 			"        checkSession();\n"
3120 			"        timerID = setInterval('checkSession()', %d);\n"
3121 			"      }\n"
3122 			"\n"
3123 			"      function receiveMessage(e) {\n"
3124 			"        console.debug('receiveMessage: ' + e.data + ' from ' + e.origin);\n"
3125 			"        if (e.origin !== targetOrigin ) {\n"
3126 			"          console.debug('receiveMessage: cross-site scripting attack?');\n"
3127 			"          return;\n"
3128 			"        }\n"
3129 			"        if (e.data != 'unchanged') {\n"
3130 			"          clearInterval(timerID);\n"
3131 			"          if (e.data == 'changed' && sessionId == '' ) {\n"
3132 			"			 // 'changed' + no session: enforce a login (if we have a login url...)\n"
3133 			"            if (loginUrl != '') {\n"
3134 			"              window.top.location.replace(loginUrl);\n"
3135 			"            }\n"
3136 			"		   } else {\n"
3137 			"              // either 'changed' + active session, or 'error': enforce a logout\n"
3138 			"              window.top.location.replace('%s?logout=' + encodeURIComponent(window.top.location.href));\n"
3139 			"          }\n"
3140 			"        }\n"
3141 			"      }\n"
3142 			"\n"
3143 			"      window.addEventListener('message', receiveMessage, false);\n"
3144 			"\n"
3145 			"    </script>\n";
3146 
3147 	/* determine the origin for the check_session_iframe endpoint */
3148 	char *origin = apr_pstrdup(r->pool, check_session_iframe);
3149 	apr_uri_t uri;
3150 	apr_uri_parse(r->pool, check_session_iframe, &uri);
3151 	char *p = strstr(origin, uri.path);
3152 	*p = '\0';
3153 
3154 	/* the element identifier for the OP iframe */
3155 	const char *op_iframe_id = "openidc-op";
3156 
3157 	/* restore the OP session_state from the session */
3158 	const char *session_state = oidc_session_get_session_state(r, session);
3159 	if (session_state == NULL) {
3160 		oidc_warn(r,
3161 				"no session_state found in the session; the OP does probably not support session management!?");
3162 		//return OK;
3163 	}
3164 
3165 	char *s_poll_interval = NULL;
3166 	oidc_util_get_request_parameter(r, "poll", &s_poll_interval);
3167 	int poll_interval = s_poll_interval ? strtol(s_poll_interval, NULL, 10) : 0;
3168 	if ((poll_interval <= 0) || (poll_interval > 3600 * 24))
3169 		poll_interval = 3000;
3170 
3171 	char *login_uri = NULL, *error_str = NULL, *error_description = NULL;
3172 	oidc_util_get_request_parameter(r, "login_uri", &login_uri);
3173 	if ((login_uri != NULL)
3174 			&& (oidc_validate_redirect_url(r, c, login_uri, FALSE, &error_str,
3175 					&error_description) == FALSE)) {
3176 		return HTTP_BAD_REQUEST;
3177 	}
3178 
3179 	const char *redirect_uri = oidc_get_redirect_uri(r, c);
3180 
3181 	java_script = apr_psprintf(r->pool, java_script, origin, client_id,
3182 			session_state ? session_state : "", login_uri ? login_uri : "",
3183 					op_iframe_id, poll_interval, redirect_uri, redirect_uri);
3184 
3185 	return oidc_util_html_send(r, NULL, java_script, "setTimer", NULL, OK);
3186 }
3187 
3188 /*
3189  * handle session management request
3190  */
oidc_handle_session_management(request_rec * r,oidc_cfg * c,oidc_session_t * session)3191 static int oidc_handle_session_management(request_rec *r, oidc_cfg *c,
3192 		oidc_session_t *session) {
3193 	char *cmd = NULL;
3194 	const char *id_token_hint = NULL;
3195 	oidc_provider_t *provider = NULL;
3196 
3197 	/* get the command passed to the session management handler */
3198 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_SESSION, &cmd);
3199 	if (cmd == NULL) {
3200 		oidc_error(r, "session management handler called with no command");
3201 		return HTTP_INTERNAL_SERVER_ERROR;
3202 	}
3203 
3204 	/* see if this is a local logout during session management */
3205 	if (apr_strnatcmp("logout", cmd) == 0) {
3206 		oidc_debug(r,
3207 				"[session=logout] calling oidc_handle_logout_request because of session mgmt local logout call.");
3208 		return oidc_handle_logout_request(r, c, session, c->default_slo_url);
3209 	}
3210 
3211 	if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) {
3212 		if ((oidc_provider_static_config(r, c, &provider) == FALSE)
3213 				|| (provider == NULL))
3214 			return HTTP_NOT_FOUND;
3215 	}
3216 
3217 	/* see if this is a request for the OP iframe */
3218 	if (apr_strnatcmp("iframe_op", cmd) == 0) {
3219 		if (provider->check_session_iframe != NULL) {
3220 			return oidc_handle_session_management_iframe_op(r, c, session,
3221 					provider->check_session_iframe);
3222 		}
3223 		return HTTP_NOT_FOUND;
3224 	}
3225 
3226 	/* see if this is a request for the RP iframe */
3227 	if (apr_strnatcmp("iframe_rp", cmd) == 0) {
3228 		if ((provider->client_id != NULL)
3229 				&& (provider->check_session_iframe != NULL)) {
3230 			return oidc_handle_session_management_iframe_rp(r, c, session,
3231 					provider->client_id, provider->check_session_iframe);
3232 		}
3233 		oidc_debug(r,
3234 				"iframe_rp command issued but no client (%s) and/or no check_session_iframe (%s) set",
3235 				provider->client_id, provider->check_session_iframe);
3236 		return HTTP_NOT_FOUND;
3237 	}
3238 
3239 	/* see if this is a request check the login state with the OP */
3240 	if (apr_strnatcmp("check", cmd) == 0) {
3241 		id_token_hint = oidc_session_get_idtoken(r, session);
3242 		/*
3243 		 * TODO: this doesn't work with per-path provided auth_request_params and scopes
3244 		 *       as oidc_dir_cfg_path_auth_request_params and oidc_dir_cfg_path_scope will pick
3245 		 *       those for the redirect_uri itself; do we need to store those as part of the
3246 		 *       session now?
3247 		 */
3248 		return oidc_authenticate_user(r, c, provider,
3249 				apr_psprintf(r->pool, "%s?session=iframe_rp",
3250 						oidc_get_redirect_uri_iss(r, c, provider)), NULL,
3251 						id_token_hint, "none", oidc_dir_cfg_path_auth_request_params(r),
3252 						oidc_dir_cfg_path_scope(r));
3253 	}
3254 
3255 	/* handle failure in fallthrough */
3256 	oidc_error(r, "unknown command: %s", cmd);
3257 
3258 	return HTTP_INTERNAL_SERVER_ERROR;
3259 }
3260 
3261 /*
3262  * handle refresh token request
3263  */
oidc_handle_refresh_token_request(request_rec * r,oidc_cfg * c,oidc_session_t * session)3264 static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c,
3265 		oidc_session_t *session) {
3266 
3267 	char *return_to = NULL;
3268 	char *r_access_token = NULL;
3269 	char *error_code = NULL;
3270 	char *error_str = NULL;
3271 	char *error_description = NULL;
3272 	apr_byte_t needs_save = TRUE;
3273 
3274 	/* get the command passed to the session management handler */
3275 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH,
3276 			&return_to);
3277 	oidc_util_get_request_parameter(r, OIDC_PROTO_ACCESS_TOKEN,
3278 			&r_access_token);
3279 
3280 	/* check the input parameters */
3281 	if (return_to == NULL) {
3282 		oidc_error(r,
3283 				"refresh token request handler called with no URL to return to");
3284 		return HTTP_INTERNAL_SERVER_ERROR;
3285 	}
3286 
3287 	/* do input validation on the return to parameter value */
3288 	if (oidc_validate_redirect_url(r, c, return_to, TRUE, &error_str,
3289 			&error_description) == FALSE) {
3290 		oidc_error(r, "return_to URL validation failed: %s: %s", error_str,
3291 				error_description);
3292 		return HTTP_INTERNAL_SERVER_ERROR;
3293 	}
3294 
3295 	if (r_access_token == NULL) {
3296 		oidc_error(r,
3297 				"refresh token request handler called with no access_token parameter");
3298 		error_code = "no_access_token";
3299 		goto end;
3300 	}
3301 
3302 	const char *s_access_token = oidc_session_get_access_token(r, session);
3303 	if (s_access_token == NULL) {
3304 		oidc_error(r,
3305 				"no existing access_token found in the session, nothing to refresh");
3306 		error_code = "no_access_token_exists";
3307 		goto end;
3308 	}
3309 
3310 	/* compare the access_token parameter used for XSRF protection */
3311 	if (apr_strnatcmp(s_access_token, r_access_token) != 0) {
3312 		oidc_error(r,
3313 				"access_token passed in refresh request does not match the one stored in the session");
3314 		error_code = "no_access_token_match";
3315 		goto end;
3316 	}
3317 
3318 	/* get a handle to the provider configuration */
3319 	oidc_provider_t *provider = NULL;
3320 	if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) {
3321 		error_code = "session_corruption";
3322 		goto end;
3323 	}
3324 
3325 	/* execute the actual refresh grant */
3326 	if (oidc_refresh_access_token(r, c, session, provider, NULL) == FALSE) {
3327 		oidc_error(r, "access_token could not be refreshed");
3328 		error_code = "refresh_failed";
3329 		goto end;
3330 	}
3331 
3332 	/* pass the tokens to the application, possibly updating the expiry */
3333 	if (oidc_session_pass_tokens(r, c, session, &needs_save) == FALSE) {
3334 		error_code = "session_corruption";
3335 		goto end;
3336 	}
3337 
3338 	if (oidc_session_save(r, session, FALSE) == FALSE) {
3339 		error_code = "error saving session";
3340 		goto end;
3341 	}
3342 
3343 end:
3344 
3345 	/* pass optional error message to the return URL */
3346 	if (error_code != NULL)
3347 		return_to = apr_psprintf(r->pool, "%s%serror_code=%s", return_to,
3348 				strchr(return_to ? return_to : "", OIDC_CHAR_QUERY) ?
3349 						OIDC_STR_AMP :
3350 						OIDC_STR_QUERY, oidc_util_escape_string(r, error_code));
3351 
3352 	/* add the redirect location header */
3353 	oidc_util_hdr_out_location_set(r, return_to);
3354 
3355 	return HTTP_MOVED_TEMPORARILY;
3356 }
3357 
3358 /*
3359  * handle request object by reference request
3360  */
oidc_handle_request_uri(request_rec * r,oidc_cfg * c)3361 static int oidc_handle_request_uri(request_rec *r, oidc_cfg *c) {
3362 
3363 	char *request_ref = NULL;
3364 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI,
3365 			&request_ref);
3366 	if (request_ref == NULL) {
3367 		oidc_error(r, "no \"%s\" parameter found",
3368 				OIDC_REDIRECT_URI_REQUEST_REQUEST_URI);
3369 		return HTTP_BAD_REQUEST;
3370 	}
3371 
3372 	char *jwt = NULL;
3373 	oidc_cache_get_request_uri(r, request_ref, &jwt);
3374 	if (jwt == NULL) {
3375 		oidc_error(r, "no cached JWT found for %s reference: %s",
3376 				OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, request_ref);
3377 		return HTTP_NOT_FOUND;
3378 	}
3379 
3380 	oidc_cache_set_request_uri(r, request_ref, NULL, 0);
3381 
3382 	return oidc_util_http_send(r, jwt, strlen(jwt), OIDC_CONTENT_TYPE_JWT, OK);
3383 }
3384 
3385 /*
3386  * handle a request to invalidate a cached access token introspection result
3387  */
oidc_handle_remove_at_cache(request_rec * r,oidc_cfg * c)3388 int oidc_handle_remove_at_cache(request_rec *r, oidc_cfg *c) {
3389 	char *access_token = NULL;
3390 	oidc_util_get_request_parameter(r,
3391 			OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, &access_token);
3392 
3393 	char *cache_entry = NULL;
3394 	oidc_cache_get_access_token(r, access_token, &cache_entry);
3395 	if (cache_entry == NULL) {
3396 		oidc_error(r, "no cached access token found for value: %s",
3397 				access_token);
3398 		return HTTP_NOT_FOUND;
3399 	}
3400 
3401 	oidc_cache_set_access_token(r, access_token, NULL, 0);
3402 
3403 	return OK;
3404 }
3405 
3406 #define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval"
3407 
3408 /*
3409  * handle request for session info
3410  */
oidc_handle_info_request(request_rec * r,oidc_cfg * c,oidc_session_t * session,apr_byte_t needs_save)3411 static int oidc_handle_info_request(request_rec *r, oidc_cfg *c,
3412 		oidc_session_t *session, apr_byte_t needs_save) {
3413 	int rc = HTTP_UNAUTHORIZED;
3414 	char *s_format = NULL, *s_interval = NULL, *r_value = NULL;
3415 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO,
3416 			&s_format);
3417 	oidc_util_get_request_parameter(r,
3418 			OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval);
3419 
3420 	/* see if this is a request for a format that is supported */
3421 	if ((apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) != 0)
3422 			&& (apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_HTML, s_format) != 0)) {
3423 		oidc_warn(r, "request for unknown format: %s", s_format);
3424 		return HTTP_UNSUPPORTED_MEDIA_TYPE;
3425 	}
3426 
3427 	/* check that we actually have a user session and this is someone calling with a proper session cookie */
3428 	if (session->remote_user == NULL) {
3429 		oidc_warn(r, "no user session found");
3430 		return HTTP_UNAUTHORIZED;
3431 	}
3432 
3433 	/* set the user in the main request for further (incl. sub-request and authz) processing */
3434 	r->user = apr_pstrdup(r->pool, session->remote_user);
3435 
3436 	if (c->info_hook_data == NULL) {
3437 		oidc_warn(r, "no data configured to return in " OIDCInfoHook);
3438 		return HTTP_NOT_FOUND;
3439 	}
3440 
3441 	/* see if we can and need to refresh the access token */
3442 	if ((s_interval != NULL)
3443 			&& (oidc_session_get_refresh_token(r, session) != NULL)) {
3444 
3445 		apr_time_t t_interval;
3446 		if (sscanf(s_interval, "%" APR_TIME_T_FMT, &t_interval) == 1) {
3447 			t_interval = apr_time_from_sec(t_interval);
3448 
3449 			/* get the last refresh timestamp from the session info */
3450 			apr_time_t last_refresh =
3451 					oidc_session_get_access_token_last_refresh(r, session);
3452 
3453 			oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
3454 					apr_time_sec(last_refresh + t_interval - apr_time_now()));
3455 
3456 			/* see if we need to refresh again */
3457 			if (last_refresh + t_interval < apr_time_now()) {
3458 
3459 				/* get the current provider info */
3460 				oidc_provider_t *provider = NULL;
3461 				if (oidc_get_provider_from_session(r, c, session, &provider)
3462 						== FALSE)
3463 					return HTTP_INTERNAL_SERVER_ERROR;
3464 
3465 				/* execute the actual refresh grant */
3466 				if (oidc_refresh_access_token(r, c, session, provider,
3467 						NULL) == FALSE)
3468 					oidc_warn(r, "access_token could not be refreshed");
3469 				else
3470 					needs_save = TRUE;
3471 			}
3472 		}
3473 	}
3474 
3475 	/* create the JSON object */
3476 	json_t *json = json_object();
3477 
3478 	/* add a timestamp of creation in there for the caller */
3479 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_TIMESTAMP,
3480 			APR_HASH_KEY_STRING)) {
3481 		json_object_set_new(json, OIDC_HOOK_INFO_TIMESTAMP,
3482 				json_integer(apr_time_sec(apr_time_now())));
3483 	}
3484 
3485 	/*
3486 	 * refresh the claims from the userinfo endpoint
3487 	 * side-effect is that this may refresh the access token if not already done
3488 	 * note that OIDCUserInfoRefreshInterval should be set to control the refresh policy
3489 	 */
3490 	needs_save |= oidc_refresh_claims_from_userinfo_endpoint(r, c, session);
3491 
3492 	/* include the access token in the session info */
3493 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN,
3494 			APR_HASH_KEY_STRING)) {
3495 		const char *access_token = oidc_session_get_access_token(r, session);
3496 		if (access_token != NULL)
3497 			json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN,
3498 					json_string(access_token));
3499 	}
3500 
3501 	/* include the access token expiry timestamp in the session info */
3502 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
3503 			APR_HASH_KEY_STRING)) {
3504 		const char *access_token_expires =
3505 				oidc_session_get_access_token_expires(r, session);
3506 		if (access_token_expires != NULL)
3507 			json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
3508 					json_string(access_token_expires));
3509 	}
3510 
3511 	/* include the id_token claims in the session info */
3512 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ID_TOKEN,
3513 			APR_HASH_KEY_STRING)) {
3514 		json_t *id_token = oidc_session_get_idtoken_claims_json(r, session);
3515 		if (id_token)
3516 			json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN, id_token);
3517 	}
3518 
3519 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_USER_INFO,
3520 			APR_HASH_KEY_STRING)) {
3521 		/* include the claims from the userinfo endpoint the session info */
3522 		json_t *claims = oidc_session_get_userinfo_claims_json(r, session);
3523 		if (claims)
3524 			json_object_set_new(json, OIDC_HOOK_INFO_USER_INFO, claims);
3525 	}
3526 
3527 	/* include the maximum session lifetime in the session info */
3528 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION_EXP,
3529 			APR_HASH_KEY_STRING)) {
3530 		apr_time_t session_expires = oidc_session_get_session_expires(r,
3531 				session);
3532 		json_object_set_new(json, OIDC_HOOK_INFO_SESSION_EXP,
3533 				json_integer(apr_time_sec(session_expires)));
3534 	}
3535 
3536 	/* include the inactivity timeout in the session info */
3537 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION_TIMEOUT,
3538 			APR_HASH_KEY_STRING)) {
3539 		json_object_set_new(json, OIDC_HOOK_INFO_SESSION_TIMEOUT,
3540 				json_integer(apr_time_sec(session->expiry)));
3541 	}
3542 
3543 	/* include the remote_user in the session info */
3544 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION_REMOTE_USER,
3545 			APR_HASH_KEY_STRING)) {
3546 		json_object_set_new(json, OIDC_HOOK_INFO_SESSION_REMOTE_USER,
3547 				json_string(session->remote_user));
3548 	}
3549 
3550 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION,
3551 			APR_HASH_KEY_STRING)) {
3552 		json_t *j_session = json_object();
3553 		json_object_set(j_session, OIDC_HOOK_INFO_SESSION_STATE,
3554 				session->state);
3555 		json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_UUID,
3556 				json_string(session->uuid));
3557 		json_object_set_new(json, OIDC_HOOK_INFO_SESSION, j_session);
3558 
3559 	}
3560 
3561 	if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_REFRESH_TOKEN,
3562 			APR_HASH_KEY_STRING)) {
3563 		/* include the refresh token in the session info */
3564 		const char *refresh_token = oidc_session_get_refresh_token(r, session);
3565 		if (refresh_token != NULL)
3566 			json_object_set_new(json, OIDC_HOOK_INFO_REFRESH_TOKEN,
3567 					json_string(refresh_token));
3568 	}
3569 
3570 	/* pass the tokens to the application and save the session, possibly updating the expiry */
3571 	if (oidc_session_pass_tokens(r, c, session, &needs_save) == FALSE)
3572 		oidc_warn(r, "error passing tokens");
3573 
3574 	/* check if something was updated in the session and we need to save it again */
3575 	if (needs_save) {
3576 		if (oidc_session_save(r, session, FALSE) == FALSE) {
3577 			oidc_warn(r, "error saving session");
3578 			rc = HTTP_INTERNAL_SERVER_ERROR;
3579 		}
3580 	}
3581 
3582 	if (apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) == 0) {
3583 		/* JSON-encode the result */
3584 		r_value = oidc_util_encode_json_object(r, json, 0);
3585 		/* return the stringified JSON result */
3586 		rc = oidc_util_http_send(r, r_value, strlen(r_value),
3587 				OIDC_CONTENT_TYPE_JSON, OK);
3588 	} else if (apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_HTML, s_format) == 0) {
3589 		/* JSON-encode the result */
3590 		r_value = oidc_util_encode_json_object(r, json, JSON_INDENT(2));
3591 		rc = oidc_util_html_send(r, "Session Info", NULL, NULL,
3592 				apr_psprintf(r->pool, "<pre>%s</pre>", r_value), OK);
3593 	}
3594 
3595 	/* free the allocated resources */
3596 	json_decref(json);
3597 
3598 	return rc;
3599 }
3600 
3601 /*
3602  * handle all requests to the redirect_uri
3603  */
oidc_handle_redirect_uri_request(request_rec * r,oidc_cfg * c,oidc_session_t * session)3604 int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c,
3605 		oidc_session_t *session) {
3606 
3607 	if (oidc_proto_is_redirect_authorization_response(r, c)) {
3608 
3609 		/* this is an authorization response from the OP using the Basic Client profile or a Hybrid flow*/
3610 		return oidc_handle_redirect_authorization_response(r, c, session);
3611 		/*
3612 		 *
3613 		 * Note that we are checking for logout *before* checking for a POST authorization response
3614 		 * to handle backchannel POST-based logout
3615 		 *
3616 		 * so any POST to the Redirect URI that does not have a logout query parameter will be handled
3617 		 * as an authorization response; alternatively we could assume that a POST response has no
3618 		 * parameters
3619 		 */
3620 	} else if (oidc_util_request_has_parameter(r,
3621 			OIDC_REDIRECT_URI_REQUEST_LOGOUT)) {
3622 		/* handle logout */
3623 		return oidc_handle_logout(r, c, session);
3624 
3625 	} else if (oidc_proto_is_post_authorization_response(r, c)) {
3626 
3627 		/* this is an authorization response using the fragment(+POST) response_mode with the Implicit Client profile */
3628 		return oidc_handle_post_authorization_response(r, c, session);
3629 
3630 	} else if (oidc_is_discovery_response(r, c)) {
3631 
3632 		/* this is response from the OP discovery page */
3633 		return oidc_handle_discovery_response(r, c);
3634 
3635 	} else if (oidc_util_request_has_parameter(r,
3636 			OIDC_REDIRECT_URI_REQUEST_JWKS)) {
3637 		/*
3638 		 * Will be handled in the content handler; avoid:
3639 		 * No authentication done but request not allowed without authentication
3640 		 * by setting r->user
3641 		 */
3642 		r->user = "";
3643 		return OK;
3644 
3645 	} else if (oidc_util_request_has_parameter(r,
3646 			OIDC_REDIRECT_URI_REQUEST_SESSION)) {
3647 
3648 		/* handle session management request */
3649 		return oidc_handle_session_management(r, c, session);
3650 
3651 	} else if (oidc_util_request_has_parameter(r,
3652 			OIDC_REDIRECT_URI_REQUEST_REFRESH)) {
3653 
3654 		/* handle refresh token request */
3655 		return oidc_handle_refresh_token_request(r, c, session);
3656 
3657 	} else if (oidc_util_request_has_parameter(r,
3658 			OIDC_REDIRECT_URI_REQUEST_REQUEST_URI)) {
3659 
3660 		/* handle request object by reference request */
3661 		return oidc_handle_request_uri(r, c);
3662 
3663 	} else if (oidc_util_request_has_parameter(r,
3664 			OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) {
3665 
3666 		/* handle request to invalidate access token cache */
3667 		return oidc_handle_remove_at_cache(r, c);
3668 
3669 	} else if (oidc_util_request_has_parameter(r,
3670 			OIDC_REDIRECT_URI_REQUEST_INFO)) {
3671 
3672 		if (session->remote_user == NULL)
3673 			return HTTP_UNAUTHORIZED;
3674 
3675 		/*
3676 		 * Will be handled in the content handler; avoid:
3677 		 * No authentication done but request not allowed without authentication
3678 		 * by setting r->user
3679 		 */
3680 		r->user = "";
3681 		return OK;
3682 
3683 	} else if ((r->args == NULL) || (apr_strnatcmp(r->args, "") == 0)) {
3684 
3685 		/* this is a "bare" request to the redirect URI, indicating implicit flow using the fragment response_mode */
3686 		return oidc_proto_javascript_implicit(r, c);
3687 	}
3688 
3689 	/* this is not an authorization response or logout request */
3690 
3691 	/* check for "error" response */
3692 	if (oidc_util_request_has_parameter(r, OIDC_PROTO_ERROR)) {
3693 
3694 		//		char *error = NULL, *descr = NULL;
3695 		//		oidc_util_get_request_parameter(r, "error", &error);
3696 		//		oidc_util_get_request_parameter(r, "error_description", &descr);
3697 		//
3698 		//		/* send user facing error to browser */
3699 		//		return oidc_util_html_send_error(r, error, descr, DONE);
3700 		return oidc_handle_redirect_authorization_response(r, c, session);
3701 	}
3702 
3703 	oidc_error(r,
3704 			"The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR",
3705 			r->args);
3706 
3707 	/* something went wrong */
3708 	return oidc_util_html_send_error(r, c->error_template, "Invalid Request",
3709 			apr_psprintf(r->pool,
3710 					"The OpenID Connect callback URL received an invalid request"),
3711 					HTTP_INTERNAL_SERVER_ERROR);
3712 }
3713 
3714 #define OIDC_AUTH_TYPE_OPENID_CONNECT "openid-connect"
3715 #define OIDC_AUTH_TYPE_OPENID_OAUTH20 "oauth20"
3716 #define OIDC_AUTH_TYPE_OPENID_BOTH    "auth-openidc"
3717 
3718 /*
3719  * main routine: handle OpenID Connect authentication
3720  */
oidc_check_userid_openidc(request_rec * r,oidc_cfg * c)3721 static int oidc_check_userid_openidc(request_rec *r, oidc_cfg *c) {
3722 
3723 	if (oidc_get_redirect_uri(r, c) == NULL) {
3724 		oidc_error(r,
3725 				"configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT "\" but " OIDCRedirectURI " has not been set");
3726 		return HTTP_INTERNAL_SERVER_ERROR;
3727 	}
3728 
3729 	/* check if this is a sub-request or an initial request */
3730 	if (!ap_is_initial_req(r)) {
3731 
3732 		/* not an initial request, try to recycle what we've already established in the main request */
3733 		if (r->main != NULL)
3734 			r->user = r->main->user;
3735 		else if (r->prev != NULL)
3736 			r->user = r->prev->user;
3737 
3738 		if (r->user != NULL) {
3739 
3740 			/* this is a sub-request and we have a session (headers will have been scrubbed and set already) */
3741 			oidc_debug(r,
3742 					"recycling user '%s' from initial request for sub-request",
3743 					r->user);
3744 
3745 			/*
3746 			 * apparently request state can get lost in sub-requests, so let's see
3747 			 * if we need to restore id_token and/or claims from the session cache
3748 			 */
3749 			const char *s_id_token = oidc_request_state_get(r,
3750 					OIDC_REQUEST_STATE_KEY_IDTOKEN);
3751 			if (s_id_token == NULL) {
3752 
3753 				oidc_session_t *session = NULL;
3754 				oidc_session_load(r, &session);
3755 
3756 				oidc_copy_tokens_to_request_state(r, session, NULL, NULL);
3757 
3758 				/* free resources allocated for the session */
3759 				oidc_session_free(r, session);
3760 			}
3761 
3762 			/* strip any cookies that we need to */
3763 			oidc_strip_cookies(r);
3764 
3765 			return OK;
3766 		}
3767 		/*
3768 		 * else: not initial request, but we could not find a session, so:
3769 		 * try to load a new session as if this were the initial request
3770 		 */
3771 	}
3772 
3773 	int rc = OK;
3774 	apr_byte_t needs_save = FALSE;
3775 
3776 	/* load the session from the request state; this will be a new "empty" session if no state exists */
3777 	oidc_session_t *session = NULL;
3778 	oidc_session_load(r, &session);
3779 
3780 	/* see if the initial request is to the redirect URI; this handles potential logout too */
3781 	if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
3782 
3783 		/* handle request to the redirect_uri */
3784 		rc = oidc_handle_redirect_uri_request(r, c, session);
3785 
3786 		/* free resources allocated for the session */
3787 		oidc_session_free(r, session);
3788 
3789 		return rc;
3790 
3791 		/* initial request to non-redirect URI, check if we have an existing session */
3792 	} else if (session->remote_user != NULL) {
3793 
3794 		/* this is initial request and we already have a session */
3795 		rc = oidc_handle_existing_session(r, c, session, &needs_save);
3796 		if (rc == OK) {
3797 
3798 			/* check if something was updated in the session and we need to save it again */
3799 			if (needs_save) {
3800 				if (oidc_session_save(r, session, FALSE) == FALSE) {
3801 					oidc_warn(r, "error saving session");
3802 					rc = HTTP_INTERNAL_SERVER_ERROR;
3803 				}
3804 			}
3805 		}
3806 
3807 		/* free resources allocated for the session */
3808 		oidc_session_free(r, session);
3809 
3810 		/* strip any cookies that we need to */
3811 		oidc_strip_cookies(r);
3812 
3813 		return rc;
3814 	}
3815 
3816 	/* free resources allocated for the session */
3817 	oidc_session_free(r, session);
3818 
3819 	/*
3820 	 * else: we have no session and it is not an authorization or
3821 	 *       discovery response: just hit the default flow for unauthenticated users
3822 	 */
3823 
3824 	return oidc_handle_unauthenticated_user(r, c);
3825 }
3826 
3827 /*
3828  * main routine: handle "mixed" OIDC/OAuth authentication
3829  */
oidc_check_mixed_userid_oauth(request_rec * r,oidc_cfg * c)3830 static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg *c) {
3831 
3832 	/* get the bearer access token from the Authorization header */
3833 	const char *access_token = NULL;
3834 	if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE) {
3835 
3836 		r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_OAUTH20);
3837 		return oidc_oauth_check_userid(r, c, access_token);
3838 	}
3839 
3840 	/* no bearer token found: then treat this as a regular OIDC browser request */
3841 	r->ap_auth_type = apr_pstrdup(r->pool, OIDC_AUTH_TYPE_OPENID_CONNECT);
3842 	return oidc_check_userid_openidc(r, c);
3843 }
3844 
3845 /*
3846  * generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines
3847  */
oidc_check_user_id(request_rec * r)3848 int oidc_check_user_id(request_rec *r) {
3849 
3850 	oidc_cfg *c = ap_get_module_config(r->server->module_config,
3851 			&auth_openidc_module);
3852 
3853 	/* log some stuff about the incoming HTTP request */
3854 	oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d",
3855 			r->parsed_uri.path, r->args, ap_is_initial_req(r));
3856 
3857 	/* see if any authentication has been defined at all */
3858 	const char *current_auth = ap_auth_type(r);
3859 	if (current_auth == NULL)
3860 		return DECLINED;
3861 
3862 	/* see if we've configured OpenID Connect user authentication for this request */
3863 	if (strcasecmp(current_auth, OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) {
3864 
3865 		r->ap_auth_type = (char*) current_auth;
3866 		return oidc_check_userid_openidc(r, c);
3867 	}
3868 
3869 	/* see if we've configured OAuth 2.0 access control for this request */
3870 	if (strcasecmp(current_auth, OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
3871 
3872 		r->ap_auth_type = (char*) current_auth;
3873 		return oidc_oauth_check_userid(r, c, NULL);
3874 	}
3875 
3876 	/* see if we've configured "mixed mode" for this request */
3877 	if (strcasecmp(current_auth, OIDC_AUTH_TYPE_OPENID_BOTH) == 0)
3878 		return oidc_check_mixed_userid_oauth(r, c);
3879 
3880 	/* this is not for us but for some other handler */
3881 	return DECLINED;
3882 }
3883 
3884 /*
3885  * get the claims and id_token from request state
3886  */
oidc_authz_get_claims_and_idtoken(request_rec * r,json_t ** claims,json_t ** id_token)3887 static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims,
3888 		json_t **id_token) {
3889 
3890 	const char *s_claims = oidc_request_state_get(r,
3891 			OIDC_REQUEST_STATE_KEY_CLAIMS);
3892 	if (s_claims != NULL)
3893 		oidc_util_decode_json_object(r, s_claims, claims);
3894 
3895 	const char *s_id_token = oidc_request_state_get(r,
3896 			OIDC_REQUEST_STATE_KEY_IDTOKEN);
3897 	if (s_id_token != NULL)
3898 		oidc_util_decode_json_object(r, s_id_token, id_token);
3899 }
3900 
3901 #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
3902 
3903 /*
3904  * find out which action we need to take when encountering an unauthorized request
3905  */
oidc_handle_unauthorized_user24(request_rec * r)3906 static authz_status oidc_handle_unauthorized_user24(request_rec *r) {
3907 
3908 	oidc_debug(r, "enter");
3909 
3910 	oidc_cfg *c = ap_get_module_config(r->server->module_config,
3911 			&auth_openidc_module);
3912 
3913 	if (apr_strnatcasecmp((const char*) ap_auth_type(r),
3914 			OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
3915 		oidc_oauth_return_www_authenticate(r, "insufficient_scope",
3916 				"Different scope(s) or other claims required");
3917 		return AUTHZ_DENIED;
3918 	}
3919 
3920 	/* see if we've configured OIDCUnAutzAction for this path */
3921 	switch (oidc_dir_cfg_unautz_action(r)) {
3922 	// TODO: document that AuthzSendForbiddenOnFailure is required to return 403 FORBIDDEN
3923 	case OIDC_UNAUTZ_RETURN403:
3924 	case OIDC_UNAUTZ_RETURN401:
3925 		return AUTHZ_DENIED;
3926 		break;
3927 	case OIDC_UNAUTZ_AUTHENTICATE:
3928 		/*
3929 		 * exception handling: if this looks like a XMLHttpRequest call we
3930 		 * won't redirect the user and thus avoid creating a state cookie
3931 		 * for a non-browser (= Javascript) call that will never return from the OP
3932 		 */
3933 		if (oidc_is_xml_http_request(r) == TRUE)
3934 			return AUTHZ_DENIED;
3935 		break;
3936 	}
3937 
3938 	oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
3939 			NULL, NULL, oidc_dir_cfg_path_auth_request_params(r),
3940 			oidc_dir_cfg_path_scope(r));
3941 
3942 	const char *location = oidc_util_hdr_out_location_get(r);
3943 	if (location != NULL) {
3944 		oidc_debug(r, "send HTML refresh with authorization redirect: %s",
3945 				location);
3946 
3947 		char *html_head = apr_psprintf(r->pool,
3948 				"<meta http-equiv=\"refresh\" content=\"0; url=%s\">",
3949 				location);
3950 		oidc_util_html_send(r, "Stepup Authentication", html_head, NULL, NULL,
3951 				HTTP_UNAUTHORIZED);
3952 		/*
3953 		 * a hack for Apache 2.4 to prevent it from writing its own 401 HTML document
3954 		 * text by making ap_send_error_response in http_protocol.c return early...
3955 		 */
3956 		r->header_only = 1;
3957 	}
3958 
3959 	return AUTHZ_DENIED;
3960 }
3961 
3962 /*
3963  * generic Apache >=2.4 authorization hook for this module
3964  * handles both OpenID Connect or OAuth 2.0 in the same way, based on the claims stored in the session
3965  */
oidc_authz_checker(request_rec * r,const char * require_args,const void * parsed_require_args,oidc_authz_match_claim_fn_type match_claim_fn)3966 authz_status oidc_authz_checker(request_rec *r, const char *require_args,
3967 		const void *parsed_require_args,
3968 		oidc_authz_match_claim_fn_type match_claim_fn) {
3969 
3970 	oidc_debug(r, "enter");
3971 
3972 	/* check for anonymous access and PASS mode */
3973 	if (r->user != NULL && strlen(r->user) == 0) {
3974 		r->user = NULL;
3975 		if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS)
3976 			return AUTHZ_GRANTED;
3977 	}
3978 
3979 	/* get the set of claims from the request state (they've been set in the authentication part earlier */
3980 	json_t *claims = NULL, *id_token = NULL;
3981 	oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
3982 
3983 	/* merge id_token claims (e.g. "iss") in to claims json object */
3984 	if (claims)
3985 		oidc_util_json_merge(r, id_token, claims);
3986 
3987 	/* dispatch to the >=2.4 specific authz routine */
3988 	authz_status rc = oidc_authz_worker24(r, claims ? claims : id_token,
3989 			require_args, parsed_require_args, match_claim_fn);
3990 
3991 	/* cleanup */
3992 	if (claims)
3993 		json_decref(claims);
3994 	if (id_token)
3995 		json_decref(id_token);
3996 
3997 	if ((rc == AUTHZ_DENIED) && ap_auth_type(r))
3998 		rc = oidc_handle_unauthorized_user24(r);
3999 
4000 	return rc;
4001 }
4002 
oidc_authz_checker_claim(request_rec * r,const char * require_args,const void * parsed_require_args)4003 authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args,
4004 		const void *parsed_require_args) {
4005 	return oidc_authz_checker(r, require_args, parsed_require_args,
4006 			oidc_authz_match_claim);
4007 }
4008 
4009 #ifdef USE_LIBJQ
oidc_authz_checker_claims_expr(request_rec * r,const char * require_args,const void * parsed_require_args)4010 authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args) {
4011 	return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claims_expr);
4012 }
4013 #endif
4014 
4015 #else
4016 
4017 /*
4018  * find out which action we need to take when encountering an unauthorized request
4019  */
oidc_handle_unauthorized_user22(request_rec * r)4020 static int oidc_handle_unauthorized_user22(request_rec *r) {
4021 
4022 	oidc_cfg *c = ap_get_module_config(r->server->module_config,
4023 			&auth_openidc_module);
4024 
4025 	if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
4026 		oidc_oauth_return_www_authenticate(r, "insufficient_scope", "Different scope(s) or other claims required");
4027 		return HTTP_UNAUTHORIZED;
4028 	}
4029 
4030 	/* see if we've configured OIDCUnAutzAction for this path */
4031 	switch (oidc_dir_cfg_unautz_action(r)) {
4032 	case OIDC_UNAUTZ_RETURN403:
4033 		return HTTP_FORBIDDEN;
4034 	case OIDC_UNAUTZ_RETURN401:
4035 		return HTTP_UNAUTHORIZED;
4036 	case OIDC_UNAUTZ_AUTHENTICATE:
4037 		/*
4038 		 * exception handling: if this looks like a XMLHttpRequest call we
4039 		 * won't redirect the user and thus avoid creating a state cookie
4040 		 * for a non-browser (= Javascript) call that will never return from the OP
4041 		 */
4042 		if (oidc_is_xml_http_request(r) == TRUE)
4043 			return HTTP_UNAUTHORIZED;
4044 	}
4045 
4046 	return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
4047 			NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r));
4048 }
4049 
4050 /*
4051  * generic Apache <2.4 authorization hook for this module
4052  * handles both OpenID Connect and OAuth 2.0 in the same way, based on the claims stored in the request context
4053  */
oidc_auth_checker(request_rec * r)4054 int oidc_auth_checker(request_rec *r) {
4055 
4056 	/* check for anonymous access and PASS mode */
4057 	if (r->user != NULL && strlen(r->user) == 0) {
4058 		r->user = NULL;
4059 		if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS)
4060 			return OK;
4061 	}
4062 
4063 	/* get the set of claims from the request state (they've been set in the authentication part earlier */
4064 	json_t *claims = NULL, *id_token = NULL;
4065 	oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
4066 
4067 	/* get the Require statements */
4068 	const apr_array_header_t * const reqs_arr = ap_requires(r);
4069 
4070 	/* see if we have any */
4071 	const require_line * const reqs =
4072 			reqs_arr ? (require_line *) reqs_arr->elts : NULL;
4073 	if (!reqs_arr) {
4074 		oidc_debug(r,
4075 				"no require statements found, so declining to perform authorization.");
4076 		return DECLINED;
4077 	}
4078 
4079 	/* merge id_token claims (e.g. "iss") in to claims json object */
4080 	if (claims)
4081 		oidc_util_json_merge(r, id_token, claims);
4082 
4083 	/* dispatch to the <2.4 specific authz routine */
4084 	int rc = oidc_authz_worker22(r, claims ? claims : id_token, reqs,
4085 			reqs_arr->nelts);
4086 
4087 	/* cleanup */
4088 	if (claims)
4089 		json_decref(claims);
4090 	if (id_token)
4091 		json_decref(id_token);
4092 
4093 	if ((rc == HTTP_UNAUTHORIZED) && ap_auth_type(r))
4094 		rc = oidc_handle_unauthorized_user22(r);
4095 
4096 	return rc;
4097 }
4098 
4099 #endif
4100 
oidc_enabled(request_rec * r)4101 apr_byte_t oidc_enabled(request_rec *r) {
4102 	if (ap_auth_type(r) == NULL)
4103 		return FALSE;
4104 
4105 	if (apr_strnatcasecmp((const char*) ap_auth_type(r),
4106 			OIDC_AUTH_TYPE_OPENID_CONNECT) == 0)
4107 		return TRUE;
4108 
4109 	if (apr_strnatcasecmp((const char*) ap_auth_type(r),
4110 			OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0)
4111 		return TRUE;
4112 
4113 	if (apr_strnatcasecmp((const char*) ap_auth_type(r),
4114 			OIDC_AUTH_TYPE_OPENID_BOTH) == 0)
4115 		return TRUE;
4116 
4117 	return FALSE;
4118 }
4119 /*
4120  * handle content generating requests
4121  */
oidc_content_handler(request_rec * r)4122 int oidc_content_handler(request_rec *r) {
4123 	oidc_cfg *c = ap_get_module_config(r->server->module_config,
4124 			&auth_openidc_module);
4125 	int rc = DECLINED;
4126 	/* track if the session needs to be updated/saved into the cache */
4127 	apr_byte_t needs_save = FALSE;
4128 	oidc_session_t *session = NULL;
4129 
4130 	if (oidc_enabled(r)
4131 			&& oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
4132 
4133 		if (oidc_util_request_has_parameter(r,
4134 				OIDC_REDIRECT_URI_REQUEST_INFO)) {
4135 
4136 			oidc_session_load(r, &session);
4137 
4138 			rc = oidc_handle_existing_session(r, c, session, &needs_save);
4139 			if (rc == OK)
4140 				/* handle request for session info */
4141 				rc = oidc_handle_info_request(r, c, session, needs_save);
4142 
4143 			/* free resources allocated for the session */
4144 			oidc_session_free(r, session);
4145 
4146 		} else if (oidc_util_request_has_parameter(r,
4147 				OIDC_REDIRECT_URI_REQUEST_JWKS)) {
4148 
4149 			/* handle JWKs request */
4150 			rc = oidc_handle_jwks(r, c);
4151 		}
4152 
4153 	}
4154 
4155 	return rc;
4156 }
4157 
4158 extern const command_rec oidc_config_cmds[];
4159 
4160 module AP_MODULE_DECLARE_DATA auth_openidc_module = {
4161 		STANDARD20_MODULE_STUFF,
4162 		oidc_create_dir_config,
4163 		oidc_merge_dir_config,
4164 		oidc_create_server_config,
4165 		oidc_merge_server_config,
4166 		oidc_config_cmds,
4167 		oidc_register_hooks
4168 };
4169