Coverage Report

Created: 2026-09-01 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/handle/response.c
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
20
/***************************************************************************
21
 * Copyright (C) 2017-2026 ZmartZone Holding BV
22
 * All rights reserved.
23
 *
24
 * DISCLAIMER OF WARRANTIES:
25
 *
26
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
27
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
28
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
29
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
30
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
31
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
32
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
33
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
34
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
35
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
36
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
 *
40
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
41
 */
42
43
#include "cfg/dir.h"
44
#include "handle/handle.h"
45
#include "metrics.h"
46
#include "mod_auth_openidc.h"
47
#include "proto/proto.h"
48
#include "state.h"
49
#include "util/util.h"
50
#include "util/util_cfg.h"
51
52
/*
53
 * redirect the browser to the session logout endpoint
54
 */
55
713
static int oidc_response_redirect_parent_window_to_logout(request_rec *r, const oidc_cfg_t *c) {
56
57
713
  oidc_debug(r, "enter");
58
59
713
  const char *java_script =
60
713
      apr_psprintf(r->pool,
61
713
       "    <script type=\"text/javascript\">\n"
62
713
       "      window.top.location.href = '%s?session=logout';\n"
63
713
       "    </script>\n",
64
713
       oidc_util_html_javascript_escape(r->pool, oidc_util_url_redirect_uri(r, c)));
65
66
713
  return oidc_util_html_content_prep(r, OIDC_REQUEST_STATE_KEY_HTML, "Redirecting...", java_script, NULL, NULL);
67
713
}
68
69
/*
70
 * handle an error returned by the OP
71
 */
72
static int oidc_response_authorization_error(request_rec *r, const oidc_cfg_t *c, const oidc_proto_state_t *proto_state,
73
5.69k
               const char *error, const char *error_description) {
74
5.69k
  const char *prompt = oidc_proto_state_get_prompt(proto_state);
75
5.69k
  if (prompt != NULL)
76
713
    prompt = apr_pstrdup(r->pool, prompt);
77
5.69k
  if ((prompt != NULL) && (_oidc_strcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
78
713
    return oidc_response_redirect_parent_window_to_logout(r, c);
79
713
  }
80
4.98k
  return oidc_util_html_send_error(r, apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error),
81
4.98k
           error_description, HTTP_BAD_REQUEST);
82
5.69k
}
83
84
/* handle the browser back on an authorization response */
85
7.48k
static apr_byte_t oidc_response_browser_back(request_rec *r, const char *r_state, const oidc_session_t *session) {
86
7.48k
  const char *s_state = NULL;
87
7.48k
  const char *o_url = NULL;
88
89
  /*  see if we have an existing session and browser-back was used */
90
7.48k
  if (session->remote_user == NULL)
91
    /* no session was established yet */
92
7.23k
    return FALSE;
93
94
245
  s_state = oidc_session_get_request_state(r, session);
95
245
  if ((r_state == NULL) || (s_state == NULL) || (_oidc_strcmp(r_state, s_state) != 0))
96
    /*  state does not match with the state that was used to create  the session earlier, no replay is going
97
     * on here */
98
245
    return FALSE;
99
100
  /* get the URL that was originally accessed by the user */
101
0
  o_url = oidc_session_get_original_url(r, session);
102
  /* log the browser back event detection */
103
0
  oidc_warn(r, "browser back detected, redirecting to original URL: %s", o_url);
104
  /* go back to the URL that he originally tried to access */
105
0
  oidc_http_hdr_out_location_set(r, o_url);
106
107
  /* signal that a browser back event was detected indeed and we handled this here */
108
0
  return TRUE;
109
245
}
110
111
static char *_oidc_response_post_preserve_template_contents = NULL;
112
113
/*
114
 * send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage
115
 */
116
apr_byte_t oidc_response_post_preserve_javascript(request_rec *r, const char *location, char **javascript,
117
2.37k
              char **javascript_method) {
118
119
2.37k
  if (oidc_cfg_dir_preserve_post_get(r) == 0)
120
1.59k
    return FALSE;
121
122
780
  oidc_debug(r, "enter");
123
124
780
  oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
125
126
780
  const char *method = oidc_original_request_method(r, cfg, FALSE);
127
128
780
  if (_oidc_strcmp(method, OIDC_METHOD_FORM_POST) != 0)
129
0
    return FALSE;
130
131
  /* read the parameters that are POST-ed to us */
132
780
  apr_table_t *params = apr_table_make(r->pool, 8);
133
780
  if (oidc_util_read_post_params(r, params, FALSE, NULL) == FALSE) {
134
0
    oidc_error(r, "something went wrong when reading the POST parameters");
135
0
    return FALSE;
136
0
  }
137
138
  /* collect the pairs and join them once: appending to the accumulated string per parameter would make
139
   * the pool memory used here quadratic in the number of (client-supplied) POST parameters */
140
780
  const apr_array_header_t *arr = apr_table_elts(params);
141
780
  const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
142
780
  apr_array_header_t *pairs = apr_array_make(r->pool, arr->nelts, sizeof(const char *));
143
4.27k
  for (int i = 0; i < arr->nelts; i++) {
144
3.49k
    APR_ARRAY_PUSH(pairs, const char *) = apr_psprintf(
145
3.49k
        r->pool, "'%s': '%s'", oidc_http_url_encode(r, elts[i].key), oidc_http_url_encode(r, elts[i].val));
146
3.49k
  }
147
780
  char *json = apr_psprintf(r->pool, "{ %s }", apr_array_pstrcat(r->pool, pairs, OIDC_CHAR_COMMA));
148
149
780
  if ((oidc_cfg_post_preserve_template_get(cfg) != NULL) &&
150
0
      (oidc_util_html_send_in_template(
151
0
     r, oidc_cfg_post_preserve_template_get(cfg), &_oidc_response_post_preserve_template_contents, json,
152
0
     OIDC_POST_PRESERVE_ESCAPE_NONE, location, OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT) == OK))
153
0
    return TRUE;
154
155
780
  const char *jmethod = "preserveOnLoad()";
156
780
  const char *jscript = apr_psprintf(
157
780
      r->pool,
158
780
      "    <script type=\"text/javascript\">\n"
159
780
      "      function %s {\n"
160
780
      "        sessionStorage.setItem('mod_auth_openidc_preserve_post_params', JSON.stringify(%s));\n"
161
780
      "        %s"
162
780
      "      }\n"
163
780
      "    </script>\n",
164
780
      jmethod, json,
165
780
      location
166
780
    ? apr_psprintf(r->pool, "window.location='%s';\n", oidc_util_html_javascript_escape(r->pool, location))
167
780
    : "");
168
169
780
  if (javascript_method)
170
780
    *javascript_method = apr_pstrdup(r->pool, jmethod);
171
780
  if (javascript)
172
780
    *javascript = apr_pstrdup(r->pool, jscript);
173
174
780
  return TRUE;
175
780
}
176
177
/*
178
 * restore POST parameters on original_url from HTML5 session storage
179
 */
180
0
static int oidc_response_post_preserved_restore(request_rec *r, const char *original_url) {
181
182
0
  oidc_debug(r, "enter: original_url=%s", original_url);
183
184
0
  const char *method = "postOnLoad()";
185
0
  const char *script =
186
0
      apr_psprintf(r->pool,
187
0
       "    <script type=\"text/javascript\">\n"
188
0
       "      function str_decode(string) {\n"
189
0
       "        try {\n"
190
0
       "          result = decodeURIComponent(string);\n"
191
0
       "        } catch (e) {\n"
192
0
       "          result =  unescape(string);\n"
193
0
       "        }\n"
194
0
       "        return result;\n"
195
0
       "      }\n"
196
0
       "      function %s {\n"
197
0
       "        var mod_auth_openidc_preserve_post_params = "
198
0
       "JSON.parse(sessionStorage.getItem('mod_auth_openidc_preserve_post_params'));\n"
199
0
       "\t\t sessionStorage.removeItem('mod_auth_openidc_preserve_post_params');\n"
200
0
       "        for (var key in mod_auth_openidc_preserve_post_params) {\n"
201
0
       "          var input = document.createElement(\"input\");\n"
202
0
       "          input.type = \"hidden\";\n"
203
0
       "          input.name = str_decode(key);\n"
204
0
       "          input.value = str_decode(mod_auth_openidc_preserve_post_params[key]);\n"
205
0
       "          document.forms[0].appendChild(input);\n"
206
0
       "        }\n"
207
0
       "        document.forms[0].action = \"%s\";\n"
208
0
       "        HTMLFormElement.prototype.submit.call(document.forms[0]);\n"
209
0
       "      }\n"
210
0
       "    </script>\n",
211
0
       method, oidc_util_html_javascript_escape(r->pool, original_url));
212
213
0
  const char *body = "    <p>Restoring...</p>\n"
214
0
         "    <form method=\"post\"></form>\n";
215
216
0
  return oidc_util_html_content_prep(r, OIDC_REQUEST_STATE_KEY_HTML, "Restoring...", script, method, body);
217
0
}
218
219
1.19k
char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const char *issuer) {
220
1.19k
  return apr_psprintf(r->pool, "%s@%s", sid, issuer);
221
1.19k
}
222
223
/*
224
 * store resolved information in the session
225
 */
226
apr_byte_t oidc_response_save_in_session(request_rec *r, const oidc_cfg_t *c, oidc_session_t *session,
227
           const oidc_provider_t *provider, const char *remoteUser, const char *id_token,
228
           oidc_jwt_t *id_token_jwt, const char *s_userinfo_claims,
229
           oidc_json_t *userinfo_claims, const char *access_token,
230
           const char *access_token_type, const int expires_in, const char *refresh_token,
231
           const char *scope, const char *session_state, const char *state,
232
1.01k
           const char *original_url, const char *userinfo_jwt) {
233
234
  /* store the user in the session */
235
1.01k
  session->remote_user = apr_pstrdup(r->pool, remoteUser);
236
237
  /* set the session expiry to the inactivity timeout */
238
1.01k
  session->expiry = apr_time_now() + apr_time_from_sec(oidc_cfg_session_inactivity_timeout_get(c));
239
240
  /* store the claims payload in the id_token for later reference */
241
1.01k
  oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.json);
242
243
1.01k
  if (oidc_cfg_store_id_token_get(c)) {
244
    /* store the compact serialized representation of the id_token for later reference  */
245
1.01k
    oidc_session_set_idtoken(r, session, id_token);
246
1.01k
  }
247
248
  /* store the issuer in the session (at least needed for session mgmt and token refresh */
249
1.01k
  oidc_session_set_issuer(r, session, oidc_cfg_provider_issuer_get(provider));
250
251
  /* store the state and original URL in the session for handling browser-back more elegantly */
252
1.01k
  oidc_session_set_request_state(r, session, state);
253
1.01k
  oidc_session_set_original_url(r, session, original_url);
254
255
1.01k
  if ((session_state != NULL) && (oidc_cfg_provider_check_session_iframe_get(provider) != NULL)) {
256
    /* store the session state and required parameters session management  */
257
0
    oidc_session_set_session_state(r, session, session_state);
258
0
    oidc_debug(r,
259
0
         "session management enabled: stored session_state (%s), check_session_iframe (%s) and "
260
0
         "client_id (%s) in the session",
261
0
         session_state, oidc_cfg_provider_check_session_iframe_get(provider),
262
0
         oidc_cfg_provider_client_id_get(provider));
263
1.01k
  } else if (oidc_cfg_provider_check_session_iframe_get(provider) == NULL) {
264
1.01k
    oidc_debug(
265
1.01k
        r, "session management disabled: \"check_session_iframe\" is not set in provider configuration");
266
1.01k
  } else {
267
0
    oidc_debug(r,
268
0
         "session management disabled: no \"session_state\" value is provided in the authentication "
269
0
         "response even though \"check_session_iframe\" (%s) is set in the provider configuration",
270
0
         oidc_cfg_provider_check_session_iframe_get(provider));
271
0
  }
272
273
  /* store the, possibly, provider specific userinfo_refresh_interval for performance reasons */
274
1.01k
  oidc_session_set_userinfo_refresh_interval(r, session,
275
1.01k
               oidc_cfg_provider_userinfo_refresh_interval_get(provider));
276
277
  /* store claims resolved from userinfo endpoint */
278
1.01k
  oidc_userinfo_store_claims(r, c, session, provider, userinfo_claims, userinfo_jwt);
279
280
  /* see if we have an access_token */
281
1.01k
  if (access_token != NULL) {
282
    /* store the access_token in the session context */
283
442
    oidc_session_set_access_token(r, session, access_token);
284
    /* store the access_token in the session context */
285
442
    oidc_session_set_access_token_type(r, session, access_token_type);
286
    /* store the associated expires_in value */
287
442
    oidc_session_set_access_token_expires(r, session, expires_in);
288
    /* reset the access token refresh timestamp */
289
442
    oidc_session_set_access_token_last_refresh(r, session, apr_time_now());
290
442
  }
291
292
  /* see if we have a refresh_token */
293
1.01k
  if (refresh_token != NULL) {
294
    /* store the refresh_token in the session context */
295
0
    oidc_session_set_refresh_token(r, session, refresh_token);
296
0
  }
297
298
  /* see if a scope was returned from the token endpoint */
299
1.01k
  if (scope != NULL) {
300
    /* store the scope in the session context */
301
17
    oidc_session_set_scope(r, session, scope);
302
17
  }
303
304
  /* store max session duration in the session as a hard cut-off expiry timestamp */
305
1.01k
  apr_time_t session_expires =
306
1.01k
      (oidc_cfg_provider_session_max_duration_get(provider) == 0)
307
1.01k
    ? oidc_util_apr_time_from_sec(id_token_jwt->payload.exp)
308
1.01k
    : (apr_time_now() + apr_time_from_sec(oidc_cfg_provider_session_max_duration_get(provider)));
309
1.01k
  oidc_session_set_session_expires(r, session, session_expires);
310
311
1.01k
  oidc_debug(r, "oidc_provider_session_max_duration_get(provider) = %d, session_expires=%" APR_TIME_T_FMT,
312
1.01k
       oidc_cfg_provider_session_max_duration_get(provider), session_expires);
313
314
  /* log message about max session duration */
315
1.01k
  oidc_log_session_expires(r, "session max lifetime", session_expires);
316
317
  /* store the domain for which this session is valid */
318
1.01k
  oidc_session_set_cookie_domain(r, session,
319
1.01k
               oidc_cfg_cookie_domain_get(c)
320
1.01k
             ? oidc_cfg_cookie_domain_get(c)
321
1.01k
             : oidc_util_url_cur_host(r, oidc_cfg_x_forwarded_headers_get(c)));
322
323
1.01k
  char *sid = NULL;
324
1.01k
  char *sub = id_token_jwt->payload.sub;
325
1.01k
  oidc_debug(r, "provider->backchannel_logout_supported=%d",
326
1.01k
       oidc_cfg_provider_backchannel_logout_supported_get(provider));
327
  /* Store sid even without back-channel support because front-channel logout may supply it. */
328
1.01k
  oidc_jose_get_string(r->pool, id_token_jwt->payload.value.json, OIDC_CLAIM_SID, FALSE, &sid, NULL);
329
1.01k
  if (sid == NULL)
330
1.01k
    sid = sub;
331
1.01k
  session->sid = oidc_response_make_sid_iss_unique(r, sid, oidc_cfg_provider_issuer_get(provider));
332
333
  /* Index by sub when back-channel logout may identify the session without sid. */
334
1.01k
  if ((oidc_cfg_provider_backchannel_logout_supported_get(provider)) && (sub != NULL) &&
335
0
      (_oidc_strcmp(sid, sub) != 0))
336
0
    session->sub = oidc_response_make_sid_iss_unique(r, sub, oidc_cfg_provider_issuer_get(provider));
337
338
  /* indicate that this is a newly created session */
339
1.01k
  oidc_session_set_session_new(r, session, 1);
340
341
  /* store the session */
342
1.01k
  return oidc_session_save(r, session, OIDC_SESSION_SAVE_NEW);
343
1.01k
}
344
345
/*
346
 * restore the state that was maintained between authorization request and response in an encrypted cookie
347
 */
348
static apr_byte_t oidc_response_proto_state_restore(request_rec *r, const oidc_cfg_t *c, const char *state,
349
6.73k
                oidc_proto_state_t **proto_state) {
350
351
6.73k
  oidc_debug(r, "enter");
352
353
6.73k
  const char *cookieName = oidc_state_cookie_name(r, state);
354
355
  /* clean expired state cookies to avoid pollution */
356
6.73k
  oidc_state_cookies_clean_expired(r, c, cookieName, FALSE);
357
358
  /* get the state cookie value first */
359
6.73k
  const char *cookieValue = oidc_http_get_cookie(r, cookieName);
360
6.73k
  if (cookieValue == NULL) {
361
24
    oidc_error(r, "no \"%s\" state cookie found: check domain and samesite cookie settings", cookieName);
362
24
    return FALSE;
363
24
  }
364
365
  /* clear state cookie because we don't need it anymore */
366
6.71k
  oidc_http_set_cookie(r, cookieName, "", 0, OIDC_HTTP_COOKIE_SAMESITE_NONE(c, r));
367
368
6.71k
  *proto_state = oidc_proto_state_from_cookie(r, c, cookieValue);
369
6.71k
  if (*proto_state == NULL)
370
0
    return FALSE;
371
372
6.71k
  const char *nonce = oidc_proto_state_get_nonce(*proto_state);
373
374
  /* calculate the hash of the browser fingerprint concatenated with the nonce */
375
6.71k
  char *calc = oidc_state_browser_fingerprint(r, c, nonce);
376
  /* compare the calculated hash with the value provided in the authorization response */
377
6.71k
  if (oidc_util_strcmp_const_time(calc, state) == FALSE) {
378
0
    oidc_error(
379
0
        r,
380
0
        "calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"",
381
0
        state, calc);
382
0
    return FALSE;
383
0
  }
384
385
6.71k
  apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state);
386
387
  /* check that the timestamp is not beyond the valid interval */
388
6.71k
  if (apr_time_now() > ts + apr_time_from_sec(oidc_cfg_state_timeout_get(c))) {
389
0
    oidc_error(r, "state has expired");
390
0
    if ((oidc_cfg_default_sso_url_get(c) == NULL) ||
391
0
        (apr_table_get(r->subprocess_env, "OIDC_NO_DEFAULT_URL_ON_STATE_TIMEOUT") != NULL)) {
392
0
      oidc_util_html_send_error(
393
0
          r, "Invalid Authentication Response",
394
0
          apr_psprintf(r->pool,
395
0
           "This is due to a timeout; please restart your authentication session by "
396
0
           "re-entering the URL/bookmark you originally wanted to access: %s",
397
0
           oidc_proto_state_get_original_url(*proto_state)),
398
0
          OK);
399
0
    }
400
0
    return FALSE;
401
0
  }
402
403
  /* add the state */
404
6.71k
  oidc_proto_state_set_state(*proto_state, state);
405
406
  /* log the restored state object */
407
6.71k
  oidc_debug(r, "restored state: %s", oidc_proto_state_to_string(r, *proto_state));
408
409
  /* we've made it */
410
6.71k
  return TRUE;
411
6.71k
}
412
413
/*
414
 * helper function for basic/implicit client flows upon receiving an authorization response:
415
 * check that it matches the state stored in the browser and return the variables associated
416
 * with the state, such as original_url and OP oidc_provider_t pointer.
417
 */
418
static apr_byte_t oidc_response_match_state(request_rec *r, oidc_cfg_t *c, const char *state,
419
7.48k
              struct oidc_provider_t **provider, oidc_proto_state_t **proto_state) {
420
421
7.48k
  oidc_debug(r, "enter (state=%s)", state);
422
423
7.48k
  if ((state == NULL) || (_oidc_strcmp(state, "") == 0)) {
424
746
    oidc_error(r, "state parameter is not set");
425
746
    return FALSE;
426
746
  }
427
428
  /* check the state parameter against what we stored in a cookie */
429
6.73k
  if (oidc_response_proto_state_restore(r, c, state, proto_state) == FALSE) {
430
24
    oidc_error(r, "unable to restore state");
431
24
    return FALSE;
432
24
  }
433
434
6.71k
  *provider = oidc_get_provider_for_issuer(r, c, oidc_proto_state_get_issuer(*proto_state), FALSE);
435
436
6.71k
  return (*provider != NULL);
437
6.73k
}
438
439
/*
440
 * handle the different flows (hybrid, implicit, Authorization Code)
441
 */
442
static apr_byte_t oidc_response_flows(request_rec *r, oidc_cfg_t *c, const oidc_proto_state_t *proto_state,
443
              oidc_provider_t *provider, apr_table_t *params, const char *response_mode,
444
6.68k
              oidc_jwt_t **jwt) {
445
446
6.68k
  apr_byte_t rc = FALSE;
447
448
6.68k
  const char *requested_response_type = oidc_proto_state_get_response_type(proto_state);
449
450
  /* handle the requested response type/mode */
451
6.68k
  if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
452
6.68k
             OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN)) {
453
252
    rc = oidc_proto_response_code_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt);
454
6.43k
  } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
455
6.43k
              OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN)) {
456
195
    rc = oidc_proto_response_code_idtoken(r, c, proto_state, provider, params, response_mode, jwt);
457
6.23k
  } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
458
6.23k
              OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN)) {
459
99
    rc = oidc_proto_response_code_token(r, c, proto_state, provider, params, response_mode, jwt);
460
6.14k
  } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE)) {
461
933
    rc = oidc_proto_response_code(r, c, proto_state, provider, params, response_mode, jwt);
462
5.20k
  } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
463
5.20k
              OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN)) {
464
915
    rc = oidc_proto_response_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt);
465
4.29k
  } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) {
466
4.29k
    rc = oidc_proto_response_idtoken(r, c, proto_state, provider, params, response_mode, jwt);
467
4.29k
  } else {
468
0
    oidc_error(r, "unsupported response type: \"%s\"", requested_response_type);
469
0
  }
470
471
6.68k
  if ((rc == FALSE) && (*jwt != NULL)) {
472
73
    oidc_jwt_destroy(*jwt);
473
73
    *jwt = NULL;
474
73
  }
475
476
6.68k
  return rc;
477
6.68k
}
478
479
/*
480
 * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
481
 */
482
static apr_byte_t oidc_response_set_request_user(request_rec *r, const oidc_cfg_t *c, const oidc_provider_t *provider,
483
1.01k
             oidc_jwt_t *jwt, const oidc_json_t *userinfo_claims) {
484
485
1.01k
  const char *issuer = oidc_cfg_provider_issuer_get(provider);
486
1.01k
  char *claim_name = apr_pstrdup(r->pool, oidc_cfg_remote_user_claim_name_get(c));
487
1.01k
  int n = (int)_oidc_strlen(claim_name);
488
1.01k
  apr_byte_t post_fix_with_issuer = (n > 0) && (claim_name[n - 1] == OIDC_CHAR_AT);
489
1.01k
  if (post_fix_with_issuer == TRUE) {
490
1.01k
    claim_name[n - 1] = '\0';
491
1.01k
    issuer = (_oidc_strstr(issuer, "https://") == NULL)
492
1.01k
           ? apr_pstrdup(r->pool, issuer)
493
1.01k
           : apr_pstrdup(r->pool, issuer + _oidc_strlen("https://"));
494
1.01k
  }
495
496
  /* extract the username claim (default: "sub") from the id_token payload or user claims */
497
1.01k
  apr_byte_t rc = FALSE;
498
1.01k
  char *remote_user = NULL;
499
1.01k
  if (userinfo_claims == NULL) {
500
1.01k
    rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp,
501
1.01k
            oidc_cfg_remote_user_claim_get(c)->replace, jwt->payload.value.json,
502
1.01k
            &remote_user);
503
1.01k
  } else {
504
0
    oidc_json_t *claims = oidc_json_copy(userinfo_claims);
505
0
    oidc_json_merge(r, jwt->payload.value.json, claims);
506
0
    rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp,
507
0
            oidc_cfg_remote_user_claim_get(c)->replace, claims, &remote_user);
508
0
    oidc_json_decref(claims);
509
0
  }
510
511
1.01k
  if ((rc == FALSE) || (remote_user == NULL)) {
512
0
    oidc_error(r,
513
0
         "" OIDCRemoteUserClaim " is set to \"%s\", but could not set the remote user based on the "
514
0
         "requested claim \"%s\" and the available claims for the user",
515
0
         oidc_cfg_remote_user_claim_name_get(c), claim_name);
516
0
    return FALSE;
517
0
  }
518
519
1.01k
  if (post_fix_with_issuer == TRUE)
520
1.01k
    remote_user = apr_psprintf(r->pool, "%s%s%s", remote_user, OIDC_STR_AT, issuer);
521
522
1.01k
  r->user = apr_pstrdup(r->pool, remote_user);
523
524
1.01k
  oidc_debug(
525
1.01k
      r, "set remote_user to \"%s\" based on claim: \"%s\"%s", r->user, oidc_cfg_remote_user_claim_name_get(c),
526
1.01k
      oidc_cfg_remote_user_claim_get(c)->reg_exp
527
1.01k
    ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"",
528
1.01k
             oidc_cfg_remote_user_claim_get(c)->reg_exp, oidc_cfg_remote_user_claim_get(c)->replace)
529
1.01k
    : "");
530
531
1.01k
  return TRUE;
532
1.01k
}
533
534
static char *_oidc_response_post_restore_template_contents = NULL;
535
536
/*
537
 * handle the case where the state parameter from the authorization response could not be matched
538
 */
539
770
static int oidc_response_handle_state_mismatch(request_rec *r, const oidc_cfg_t *c) {
540
770
  if (oidc_cfg_default_sso_url_get(c) != NULL) {
541
0
    oidc_warn(r,
542
0
        "invalid authorization response state; a default SSO URL is set, sending the user there: %s",
543
0
        oidc_cfg_default_sso_url_get(c));
544
0
    oidc_http_hdr_out_location_set(r, oidc_util_url_abs(r, c, oidc_cfg_default_sso_url_get(c)));
545
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH);
546
0
    return HTTP_MOVED_TEMPORARILY;
547
0
  }
548
549
770
  oidc_error(r, "invalid authorization response state and no default SSO URL is set, sending an error...");
550
551
  // if error text was already produced (e.g. state timeout) then just return with a 400
552
770
  if (apr_table_get(r->subprocess_env, OIDC_ERROR_ENVVAR) != NULL) {
553
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_EXPIRED);
554
0
    return HTTP_BAD_REQUEST;
555
0
  }
556
557
770
  OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_STATE_MISMATCH);
558
559
770
  return oidc_util_html_send_error(r, "Invalid Authorization Response",
560
770
           "Could not match the authorization response to an earlier request via "
561
770
           "the state parameter and corresponding state cookie",
562
770
           HTTP_BAD_REQUEST);
563
770
}
564
565
/*
566
 * finalize a successful authorization response: restore preserved form-post data or redirect to the original URL
567
 */
568
static int oidc_response_finish_success(request_rec *r, const oidc_cfg_t *c, const char *original_url,
569
942
          const char *original_method) {
570
  /* log the successful response */
571
942
  oidc_debug(r, "session created and stored, returning to original URL: %s, original method: %s", original_url,
572
942
       original_method);
573
574
  /* check whether form post data was preserved; if so restore it */
575
942
  if (_oidc_strcmp(original_method, OIDC_METHOD_FORM_POST) == 0) {
576
0
    if (oidc_cfg_post_restore_template_get(c) != NULL)
577
0
      return oidc_util_html_send_in_template(
578
0
          r, oidc_cfg_post_restore_template_get(c), &_oidc_response_post_restore_template_contents,
579
0
          original_url, OIDC_POST_PRESERVE_ESCAPE_JAVASCRIPT, NULL, OIDC_POST_PRESERVE_ESCAPE_NONE);
580
0
    return oidc_response_post_preserved_restore(r, original_url);
581
0
  }
582
583
  /* now we've authenticated the user so go back to the URL that he originally tried to access */
584
942
  oidc_http_hdr_out_location_set(r, original_url);
585
586
  /* do the actual redirect to the original URL */
587
942
  return HTTP_MOVED_TEMPORARILY;
588
942
}
589
590
/*
591
 * complete the handling of an authorization response by obtaining, parsing and verifying the
592
 * id_token and storing the authenticated user state in the session
593
 */
594
static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_table_t *params,
595
7.48k
         const char *response_mode) {
596
7.48k
  int rc = -1;
597
7.48k
  oidc_provider_t *provider = NULL;
598
7.48k
  oidc_proto_state_t *proto_state = NULL;
599
7.48k
  oidc_jwt_t *id_token = NULL;
600
7.48k
  oidc_json_t *userinfo_claims = NULL;
601
7.48k
  int expires_in = 0;
602
7.48k
  char *userinfo_jwt = NULL;
603
7.48k
  const char *s_userinfo_claims = NULL;
604
7.48k
  const char *original_url = NULL;
605
7.48k
  const char *original_method = NULL;
606
7.48k
  const char *prompt = NULL;
607
608
7.48k
  oidc_debug(r, "enter, response_mode=%s", response_mode);
609
610
  /* see if this response came from a browser-back event */
611
7.48k
  if (oidc_response_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE), session) == TRUE) {
612
0
    rc = HTTP_MOVED_TEMPORARILY;
613
0
    goto end;
614
0
  }
615
616
  /* match the returned state parameter against the state stored in the browser */
617
7.48k
  if (oidc_response_match_state(r, c, apr_table_get(params, OIDC_PROTO_STATE), &provider, &proto_state) ==
618
7.48k
      FALSE) {
619
770
    rc = oidc_response_handle_state_mismatch(r, c);
620
770
    goto end;
621
770
  }
622
623
  /* see if the response is an error response */
624
6.71k
  if (apr_table_get(params, OIDC_PROTO_ERROR) != NULL) {
625
26
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROVIDER);
626
26
    rc = oidc_response_authorization_error(r, c, proto_state, apr_table_get(params, OIDC_PROTO_ERROR),
627
26
                   apr_table_get(params, OIDC_PROTO_ERROR_DESCRIPTION));
628
26
    goto end;
629
26
  }
630
631
  /* handle the code, implicit or hybrid flow */
632
6.68k
  if (oidc_response_flows(r, c, proto_state, provider, params, response_mode, &id_token) == FALSE) {
633
5.66k
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROTOCOL);
634
5.66k
    rc = oidc_response_authorization_error(r, c, proto_state, "Error in handling response type.", NULL);
635
5.66k
    goto end;
636
5.66k
  }
637
638
1.01k
  if (id_token == NULL) {
639
0
    oidc_error(r, "no id_token was provided");
640
0
    rc = oidc_response_authorization_error(r, c, proto_state, "No id_token was provided.", NULL);
641
0
    goto end;
642
0
  }
643
644
1.01k
  expires_in = _oidc_str_to_int(apr_table_get(params, OIDC_PROTO_EXPIRES_IN), -1);
645
646
  /*
647
   * optionally resolve additional claims against the userinfo endpoint
648
   * parsed claims are not actually used here but need to be parsed anyway for error checking purposes
649
   */
650
1.01k
  s_userinfo_claims = oidc_userinfo_retrieve_claims(
651
1.01k
      r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN),
652
1.01k
      apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), NULL, id_token->payload.sub, &userinfo_claims, &userinfo_jwt);
653
654
  /* restore the original protected URL that the user was trying to access */
655
1.01k
  original_url = oidc_proto_state_get_original_url(proto_state);
656
1.01k
  if (original_url != NULL)
657
1.01k
    original_url = apr_pstrdup(r->pool, original_url);
658
1.01k
  original_method = oidc_proto_state_get_original_method(proto_state);
659
1.01k
  if (original_method != NULL)
660
1.01k
    original_method = apr_pstrdup(r->pool, original_method);
661
1.01k
  prompt = oidc_proto_state_get_prompt(proto_state);
662
663
  /* set the user */
664
1.01k
  if (oidc_response_set_request_user(r, c, provider, id_token, userinfo_claims) == FALSE) {
665
0
    oidc_error(r, "remote user could not be set");
666
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER);
667
0
    rc = oidc_response_authorization_error(
668
0
        r, c, proto_state, "Remote user could not be set: contact the website administrator", NULL);
669
0
    goto end;
670
0
  }
671
672
1.01k
  oidc_debug(r, "set remote_user to \"%s\" in new session \"%s\"", r->user, session->uuid);
673
674
  /* For prompt=none, reject a different existing remote user. With no prior user, establish a new session. */
675
1.01k
  if ((prompt != NULL) && (_oidc_strcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0) && (session->remote_user != NULL) &&
676
0
      (_oidc_strcmp(session->remote_user, r->user) != 0)) {
677
0
    oidc_warn(r, "user set from new id_token is different from current one");
678
0
    rc = oidc_response_authorization_error(r, c, proto_state, "User changed!", NULL);
679
0
    goto end;
680
0
  }
681
682
  /* Reset after checks that need the old session and before storing the new authentication. */
683
1.01k
  oidc_session_reset(r, c, session);
684
685
  /* Persist per-path settings only when session management may reuse them for silent reauthentication. */
686
1.01k
  if (oidc_cfg_provider_check_session_iframe_get(provider) != NULL) {
687
0
    oidc_session_set_path_auth_request_params(r, session,
688
0
                oidc_proto_state_get_auth_request_params(proto_state));
689
0
    oidc_session_set_path_scope(r, session, oidc_proto_state_get_path_scope(proto_state));
690
0
  }
691
692
  /* store resolved information in the session */
693
1.01k
  if (oidc_response_save_in_session(
694
1.01k
    r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), id_token,
695
1.01k
    s_userinfo_claims, userinfo_claims, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN),
696
1.01k
    apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), expires_in,
697
1.01k
    apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN), apr_table_get(params, OIDC_PROTO_SCOPE),
698
1.01k
    apr_table_get(params, OIDC_PROTO_SESSION_STATE), apr_table_get(params, OIDC_PROTO_STATE), original_url,
699
1.01k
    userinfo_jwt) == FALSE) {
700
77
    rc = HTTP_INTERNAL_SERVER_ERROR;
701
77
    goto end;
702
77
  }
703
704
  /* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */
705
942
  if (r->user == NULL) {
706
0
    OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER);
707
0
    rc = HTTP_UNAUTHORIZED;
708
0
    goto end;
709
0
  }
710
711
942
  rc = oidc_response_finish_success(r, c, original_url, original_method);
712
713
7.48k
end:
714
715
7.48k
  if (proto_state)
716
6.71k
    oidc_proto_state_destroy(proto_state);
717
7.48k
  if (id_token)
718
1.01k
    oidc_jwt_destroy(id_token);
719
7.48k
  if (userinfo_claims)
720
0
    oidc_json_decref(userinfo_claims);
721
722
7.48k
  return rc;
723
942
}
724
725
/* specification-defined parameters that must not be repeated in an authorization response */
726
static const char *const OIDC_RESPONSE_NO_REPEAT[] = {OIDC_PROTO_STATE, OIDC_PROTO_CODE, OIDC_PROTO_ACCESS_TOKEN,
727
                  OIDC_PROTO_ID_TOKEN, NULL};
728
729
/*
730
 * handle an OpenID Connect Authorization Response using the POST (+fragment->POST) response_mode
731
 */
732
1.50k
int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
733
734
1.50k
  oidc_debug(r, "enter");
735
736
  /* initialize local variables */
737
1.50k
  const char *response_mode = NULL;
738
739
  /* read the parameters that are POST-ed to us */
740
1.50k
  apr_table_t *params = apr_table_make(r->pool, 8);
741
1.50k
  if (oidc_util_read_post_params_reject_dup(r, params, FALSE, NULL, OIDC_RESPONSE_NO_REPEAT) == FALSE) {
742
25
    oidc_error(r, "something went wrong when reading the POST parameters");
743
25
    return HTTP_BAD_REQUEST;
744
25
  }
745
746
  /* see if we've got any POST-ed data at all */
747
1.47k
  if ((apr_table_elts(params)->nelts < 1) ||
748
1.46k
      ((apr_table_elts(params)->nelts == 1) && apr_table_get(params, OIDC_PROTO_RESPONSE_MODE) &&
749
75
       (_oidc_strcmp(apr_table_get(params, OIDC_PROTO_RESPONSE_MODE), OIDC_PROTO_RESPONSE_MODE_FRAGMENT) == 0))) {
750
10
    return oidc_util_html_send_error(
751
10
        r, "Invalid Request",
752
10
        "You've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you "
753
10
        "should not open this URL in your browser directly, or have the server administrator use a "
754
10
        "different " OIDCRedirectURI " setting.",
755
10
        HTTP_INTERNAL_SERVER_ERROR);
756
10
  }
757
758
  /* get the parameters */
759
1.46k
  response_mode = apr_table_get(params, OIDC_PROTO_RESPONSE_MODE);
760
761
  /* do the actual implicit work */
762
1.46k
  return oidc_response_process(r, c, session, params,
763
1.46k
             response_mode ? response_mode : OIDC_PROTO_RESPONSE_MODE_FORM_POST);
764
1.47k
}
765
766
/*
767
 * handle an OpenID Connect Authorization Response using the redirect response_mode
768
 */
769
6.08k
int oidc_response_authorization_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
770
771
6.08k
  oidc_debug(r, "enter");
772
773
  /* read the parameters from the query string */
774
6.08k
  apr_table_t *params = apr_table_make(r->pool, 8);
775
6.08k
  if (oidc_util_read_form_encoded_params_reject_dup(r, params, r->args, OIDC_RESPONSE_NO_REPEAT) == FALSE)
776
73
    return HTTP_BAD_REQUEST;
777
778
  /* do the actual work */
779
6.01k
  return oidc_response_process(r, c, session, params, OIDC_PROTO_RESPONSE_MODE_QUERY);
780
6.08k
}