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&%s=%s&%s=%s&%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&%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&%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. "mike@seed.gluu.org", or an IDP identifier (eg. "mitreid.org"):</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