Coverage Report

Created: 2026-11-12 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/proto/request.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 "metadata.h"
45
#include "metrics.h"
46
#include "proto/proto.h"
47
#include "util/util.h"
48
49
/*
50
 * Configured extras may replace module-owned parameters; request-supplied values, including
51
 * "#" forwards, may not. One shared "added" table distinguishes both directives' parameters
52
 * from module-owned ones, allowing repeats such as RFC 8707 "resource" across either directive.
53
 */
54
static void oidc_proto_request_auth_params_add(request_rec *r, apr_table_t *params, apr_table_t *added,
55
2.12k
                 const char *auth_request_params, apr_byte_t configured) {
56
2.12k
  char *key = NULL;
57
2.12k
  char *val = NULL;
58
59
2.12k
  if (auth_request_params == NULL)
60
1.47k
    return;
61
62
144k
  while (*auth_request_params) {
63
144k
    apr_byte_t from_request = FALSE;
64
144k
    val = ap_getword(r->pool, &auth_request_params, OIDC_CHAR_AMP);
65
144k
    if (val == NULL)
66
0
      break;
67
144k
    key = ap_getword(r->pool, (const char **)&val, OIDC_CHAR_EQUAL);
68
144k
    ap_unescape_url(key);
69
144k
    ap_unescape_url(val);
70
144k
    if (_oidc_strcmp(val, OIDC_STR_HASH) == 0) {
71
4.30k
      if (oidc_util_url_has_parameter(r, key) == FALSE)
72
528
        continue;
73
3.77k
      oidc_util_url_parameter_get(r, key, &val);
74
3.77k
      from_request = TRUE;
75
3.77k
    }
76
143k
    if ((apr_table_get(params, key) != NULL) && (apr_table_get(added, key) == NULL)) {
77
278
      if ((configured == FALSE) || (from_request == TRUE)) {
78
278
        oidc_warn(r,
79
278
            "dropping authorization request parameter \"%s\": it would duplicate one "
80
278
            "that this module sets itself",
81
278
            key);
82
278
        continue;
83
278
      }
84
0
      apr_table_set(params, key, val);
85
0
      apr_table_setn(added, key, "");
86
0
      continue;
87
278
    }
88
143k
    apr_table_add(params, key, val);
89
143k
    apr_table_setn(added, key, "");
90
143k
  }
91
646
}
92
93
/*
94
 * send a Pushed Authorization Request (PAR) to the Provider
95
 */
96
int oidc_proto_request_auth_push(request_rec *r, oidc_cfg_t *cfg, const struct oidc_provider_t *provider,
97
0
         apr_table_t *params) {
98
0
  char *response = NULL;
99
0
  char *basic_auth = NULL;
100
0
  char *bearer_auth = NULL;
101
0
  char *request_uri = NULL;
102
0
  int expires_in = 0;
103
0
  const char *authorization_request = NULL;
104
0
  const char *scope = NULL;
105
0
  oidc_json_t *j_result = NULL;
106
0
  int rv = HTTP_INTERNAL_SERVER_ERROR;
107
0
  const char *endpoint_url = oidc_cfg_provider_pushed_authorization_request_endpoint_url_get(provider);
108
109
0
  oidc_debug(r, "enter");
110
111
0
  if (endpoint_url == NULL) {
112
0
    oidc_error(r, "the Provider's OAuth 2.0 Pushed Authorization Request endpoint URL is not set, PAR "
113
0
            "cannot be used");
114
0
    rv = oidc_util_html_send_error(
115
0
        r, "Pushed Authorization Request Endpoint not set",
116
0
        "the Provider's OAuth 2.0 Pushed Authorization Request endpoint URL is not set, PAR cannot be used",
117
0
        HTTP_INTERNAL_SERVER_ERROR);
118
0
    goto out;
119
0
  }
120
121
  /* add the token endpoint authentication credentials to the pushed authorization request */
122
0
  if (oidc_proto_token_endpoint_auth(
123
0
    r, cfg, oidc_proto_profile_token_endpoint_auth_get(cfg, provider),
124
0
    oidc_proto_profile_token_endpoint_auth_alg_get(cfg, provider),
125
0
    oidc_cfg_provider_client_id_get(provider), oidc_cfg_provider_client_secret_get(provider),
126
0
    oidc_cfg_provider_client_keys_get(provider), oidc_proto_profile_token_endpoint_auth_aud(provider),
127
0
    params, NULL, &basic_auth, &bearer_auth) == FALSE)
128
0
    goto out;
129
130
0
  OIDC_METRICS_TIMING_START(r, cfg);
131
132
0
  if (oidc_http_post_form(r, endpoint_url, params, basic_auth, bearer_auth, NULL,
133
0
        oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, NULL,
134
0
        oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg),
135
0
        oidc_cfg_dir_pass_cookies_get(r),
136
0
        oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider),
137
0
        oidc_cfg_provider_token_endpoint_tls_client_key_get(provider),
138
0
        oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)) == FALSE) {
139
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_PAR_ERROR);
140
0
    goto out;
141
0
  }
142
143
0
  OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_PAR);
144
145
  /* check for errors, the response itself will have been logged already */
146
0
  if (oidc_json_decode_and_check_error(r, response, &j_result) == FALSE) {
147
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_PAR_ERROR);
148
0
    goto out;
149
0
  }
150
151
  /* get the request_uri from the parsed response */
152
0
  oidc_json_object_get_string(r->pool, j_result, OIDC_PROTO_REQUEST_URI, &request_uri, NULL);
153
154
  /* get the expires_in value from the parsed response */
155
0
  oidc_json_object_get_int(j_result, OIDC_PROTO_EXPIRES_IN, &expires_in, 60);
156
157
  /* assemble the resulting authentication request and redirect */
158
0
  apr_table_clear(params);
159
0
  apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider));
160
0
  apr_table_setn(params, OIDC_PROTO_REQUEST_URI, request_uri);
161
  /*
162
   * OIDC requires scope=openid beside request_uri; FAPI 2.0 restricts the request to
163
   * client_id and request_uri.
164
   */
165
0
  scope = oidc_proto_profile_request_uri_scope_get(provider);
166
0
  if (scope != NULL)
167
0
    apr_table_setn(params, OIDC_PROTO_SCOPE, scope);
168
0
  authorization_request =
169
0
      oidc_http_query_encoded_url(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params);
170
0
  oidc_http_hdr_out_location_set(r, authorization_request);
171
0
  rv = HTTP_MOVED_TEMPORARILY;
172
173
0
out:
174
175
0
  if (j_result)
176
0
    oidc_json_decref(j_result);
177
178
0
  return rv;
179
0
}
180
181
/*
182
 * concatenate per-path scopes with per-provider scopes, warn if "openid" is missing, and add the result to params
183
 */
184
static void oidc_proto_request_auth_scope_set(request_rec *r, const struct oidc_provider_t *provider,
185
1.06k
                const char *path_scope, apr_table_t *params) {
186
1.06k
  const char *scope = oidc_cfg_provider_scope_get(provider);
187
1.06k
  if (path_scope != NULL)
188
85
    scope = ((scope != NULL) && (_oidc_strcmp(scope, "") != 0))
189
85
          ? apr_pstrcat(r->pool, scope, OIDC_STR_SPACE, path_scope, NULL)
190
85
          : path_scope;
191
192
1.06k
  if (scope == NULL)
193
0
    return;
194
195
1.06k
  if (!oidc_util_spaced_string_contains(r->pool, scope, OIDC_PROTO_SCOPE_OPENID))
196
1.06k
    oidc_warn(r,
197
1.06k
        "the configuration for the \"%s\" parameter does not include the \"%s\" scope, your "
198
1.06k
        "provider may not return an \"id_token\": %s",
199
1.06k
        OIDC_PROTO_SCOPE, OIDC_PROTO_SCOPE_OPENID, scope);
200
201
1.06k
  apr_table_setn(params, OIDC_PROTO_SCOPE, scope);
202
1.06k
}
203
204
/*
205
 * assemble all parameters that go into the authentication request
206
 */
207
void oidc_proto_request_auth_params_set(request_rec *r, oidc_cfg_t *cfg, const struct oidc_provider_t *provider,
208
          const char *login_hint, const char *redirect_uri, const char *state,
209
          const oidc_proto_state_t *proto_state, const char *id_token_hint,
210
          const char *code_challenge, const char *auth_request_params,
211
1.06k
          const char *path_scope, apr_table_t *params) {
212
213
  /* add the response type */
214
1.06k
  apr_table_setn(params, OIDC_PROTO_RESPONSE_TYPE, oidc_proto_state_get_response_type(proto_state));
215
216
  /* concat the per-path scopes with the per-provider scopes */
217
1.06k
  oidc_proto_request_auth_scope_set(r, provider, path_scope, params);
218
219
  /* add the client ID */
220
1.06k
  apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider));
221
222
  /* add the state */
223
1.06k
  apr_table_setn(params, OIDC_PROTO_STATE, state);
224
225
  /* add the redirect uri */
226
1.06k
  apr_table_setn(params, OIDC_PROTO_REDIRECT_URI, redirect_uri);
227
228
  /* add the nonce if set */
229
1.06k
  const char *nonce = oidc_proto_state_get_nonce(proto_state);
230
1.06k
  if (nonce != NULL)
231
1.06k
    apr_table_setn(params, OIDC_PROTO_NONCE, nonce);
232
233
  /* add PKCE code challenge if set */
234
1.06k
  if ((code_challenge != NULL) && (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none)) {
235
1.06k
    apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge);
236
1.06k
    apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD, oidc_proto_profile_pkce_get(provider)->method);
237
1.06k
  }
238
239
  /* add the response_mode if explicitly set */
240
1.06k
  const char *response_mode = oidc_proto_state_get_response_mode(proto_state);
241
1.06k
  if (response_mode != NULL)
242
0
    apr_table_setn(params, OIDC_PROTO_RESPONSE_MODE, response_mode);
243
244
  /* add the login_hint if provided */
245
1.06k
  if (login_hint != NULL)
246
147
    apr_table_setn(params, OIDC_PROTO_LOGIN_HINT, login_hint);
247
248
  /* add the id_token_hint if provided */
249
1.06k
  if (id_token_hint != NULL)
250
6
    apr_table_setn(params, OIDC_PROTO_ID_TOKEN_HINT, id_token_hint);
251
252
  /* add the prompt setting if provided (e.g. "none" for no-GUI checks) */
253
1.06k
  const char *prompt = oidc_proto_state_get_prompt(proto_state);
254
1.06k
  if (prompt != NULL)
255
9
    apr_table_setn(params, OIDC_PROTO_PROMPT, prompt);
256
257
  /* the keys the two directives below add, shared between their invocations so that a
258
   * parameter the first one added is not mistaken for one this module owns by the second */
259
1.06k
  apr_table_t *added = apr_table_make(r->pool, 4);
260
261
  /* add any statically configured custom authorization request parameters */
262
1.06k
  oidc_proto_request_auth_params_add(r, params, added, oidc_cfg_provider_auth_request_params_get(provider), TRUE);
263
264
  /* add any dynamically configured custom authorization request parameters */
265
1.06k
  oidc_proto_request_auth_params_add(r, params, added, auth_request_params, FALSE);
266
267
  /* add request parameter (request or request_uri) if set */
268
1.06k
  if (oidc_cfg_provider_request_object_get(provider) != NULL)
269
0
    oidc_proto_request_object_param_add(r, cfg, provider, redirect_uri, params);
270
1.06k
}