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