Coverage Report

Created: 2026-09-28 07:07

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.08k
                 const char *auth_request_params, apr_byte_t configured) {
56
2.08k
  char *key = NULL;
57
2.08k
  char *val = NULL;
58
59
2.08k
  if (auth_request_params == NULL)
60
1.46k
    return;
61
62
47.3k
  while (*auth_request_params) {
63
46.7k
    apr_byte_t from_request = FALSE;
64
46.7k
    val = ap_getword(r->pool, &auth_request_params, OIDC_CHAR_AMP);
65
46.7k
    if (val == NULL)
66
0
      break;
67
46.7k
    key = ap_getword(r->pool, (const char **)&val, OIDC_CHAR_EQUAL);
68
46.7k
    ap_unescape_url(key);
69
46.7k
    ap_unescape_url(val);
70
46.7k
    if (_oidc_strcmp(val, OIDC_STR_HASH) == 0) {
71
7.35k
      if (oidc_util_url_has_parameter(r, key) == FALSE)
72
636
        continue;
73
6.72k
      oidc_util_url_parameter_get(r, key, &val);
74
6.72k
      from_request = TRUE;
75
6.72k
    }
76
46.0k
    if ((apr_table_get(params, key) != NULL) && (apr_table_get(added, key) == NULL)) {
77
224
      if ((configured == FALSE) || (from_request == TRUE)) {
78
224
        oidc_warn(r,
79
224
            "dropping authorization request parameter \"%s\": it would duplicate one "
80
224
            "that this module sets itself",
81
224
            key);
82
224
        continue;
83
224
      }
84
0
      apr_table_set(params, key, val);
85
0
      apr_table_setn(added, key, "");
86
0
      continue;
87
224
    }
88
45.8k
    apr_table_add(params, key, val);
89
45.8k
    apr_table_setn(added, key, "");
90
45.8k
  }
91
617
}
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_cfg_provider_token_endpoint_auth_get(provider),
124
0
    oidc_cfg_provider_token_endpoint_auth_alg_get(provider), oidc_cfg_provider_client_id_get(provider),
125
0
    oidc_cfg_provider_client_secret_get(provider), oidc_cfg_provider_client_keys_get(provider),
126
0
    oidc_proto_profile_token_endpoint_auth_aud(provider), params, NULL, &basic_auth, &bearer_auth) == FALSE)
127
0
    goto out;
128
129
0
  OIDC_METRICS_TIMING_START(r, cfg);
130
131
0
  if (oidc_http_post_form(r, endpoint_url, params, basic_auth, bearer_auth, NULL,
132
0
        oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, NULL,
133
0
        oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg),
134
0
        oidc_cfg_dir_pass_cookies_get(r),
135
0
        oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider),
136
0
        oidc_cfg_provider_token_endpoint_tls_client_key_get(provider),
137
0
        oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)) == FALSE) {
138
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_PAR_ERROR);
139
0
    goto out;
140
0
  }
141
142
0
  OIDC_METRICS_TIMING_ADD(r, cfg, OM_PROVIDER_PAR);
143
144
  /* check for errors, the response itself will have been logged already */
145
0
  if (oidc_json_decode_and_check_error(r, response, &j_result) == FALSE) {
146
0
    OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_PAR_ERROR);
147
0
    goto out;
148
0
  }
149
150
  /* get the request_uri from the parsed response */
151
0
  oidc_json_object_get_string(r->pool, j_result, OIDC_PROTO_REQUEST_URI, &request_uri, NULL);
152
153
  /* get the expires_in value from the parsed response */
154
0
  oidc_json_object_get_int(j_result, OIDC_PROTO_EXPIRES_IN, &expires_in, 60);
155
156
  /* assemble the resulting authentication request and redirect */
157
0
  apr_table_clear(params);
158
0
  apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider));
159
0
  apr_table_setn(params, OIDC_PROTO_REQUEST_URI, request_uri);
160
  /*
161
   * OIDC requires scope=openid beside request_uri; FAPI 2.0 restricts the request to
162
   * client_id and request_uri.
163
   */
164
0
  scope = oidc_proto_profile_request_uri_scope_get(provider);
165
0
  if (scope != NULL)
166
0
    apr_table_setn(params, OIDC_PROTO_SCOPE, scope);
167
0
  authorization_request =
168
0
      oidc_http_query_encoded_url(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params);
169
0
  oidc_http_hdr_out_location_set(r, authorization_request);
170
0
  rv = HTTP_MOVED_TEMPORARILY;
171
172
0
out:
173
174
0
  if (j_result)
175
0
    oidc_json_decref(j_result);
176
177
0
  return rv;
178
0
}
179
180
/*
181
 * concatenate per-path scopes with per-provider scopes, warn if "openid" is missing, and add the result to params
182
 */
183
static void oidc_proto_request_auth_scope_set(request_rec *r, const struct oidc_provider_t *provider,
184
1.04k
                const char *path_scope, apr_table_t *params) {
185
1.04k
  const char *scope = oidc_cfg_provider_scope_get(provider);
186
1.04k
  if (path_scope != NULL)
187
60
    scope = ((scope != NULL) && (_oidc_strcmp(scope, "") != 0))
188
60
          ? apr_pstrcat(r->pool, scope, OIDC_STR_SPACE, path_scope, NULL)
189
60
          : path_scope;
190
191
1.04k
  if (scope == NULL)
192
0
    return;
193
194
1.04k
  if (!oidc_util_spaced_string_contains(r->pool, scope, OIDC_PROTO_SCOPE_OPENID))
195
1.04k
    oidc_warn(r,
196
1.04k
        "the configuration for the \"%s\" parameter does not include the \"%s\" scope, your "
197
1.04k
        "provider may not return an \"id_token\": %s",
198
1.04k
        OIDC_PROTO_SCOPE, OIDC_PROTO_SCOPE_OPENID, scope);
199
200
1.04k
  apr_table_setn(params, OIDC_PROTO_SCOPE, scope);
201
1.04k
}
202
203
/*
204
 * assemble all parameters that go into the authentication request
205
 */
206
void oidc_proto_request_auth_params_set(request_rec *r, oidc_cfg_t *cfg, const struct oidc_provider_t *provider,
207
          const char *login_hint, const char *redirect_uri, const char *state,
208
          const oidc_proto_state_t *proto_state, const char *id_token_hint,
209
          const char *code_challenge, const char *auth_request_params,
210
1.04k
          const char *path_scope, apr_table_t *params) {
211
212
  /* add the response type */
213
1.04k
  apr_table_setn(params, OIDC_PROTO_RESPONSE_TYPE, oidc_proto_state_get_response_type(proto_state));
214
215
  /* concat the per-path scopes with the per-provider scopes */
216
1.04k
  oidc_proto_request_auth_scope_set(r, provider, path_scope, params);
217
218
  /* add the client ID */
219
1.04k
  apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider));
220
221
  /* add the state */
222
1.04k
  apr_table_setn(params, OIDC_PROTO_STATE, state);
223
224
  /* add the redirect uri */
225
1.04k
  apr_table_setn(params, OIDC_PROTO_REDIRECT_URI, redirect_uri);
226
227
  /* add the nonce if set */
228
1.04k
  const char *nonce = oidc_proto_state_get_nonce(proto_state);
229
1.04k
  if (nonce != NULL)
230
1.04k
    apr_table_setn(params, OIDC_PROTO_NONCE, nonce);
231
232
  /* add PKCE code challenge if set */
233
1.04k
  if ((code_challenge != NULL) && (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none)) {
234
1.04k
    apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge);
235
1.04k
    apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD, oidc_proto_profile_pkce_get(provider)->method);
236
1.04k
  }
237
238
  /* add the response_mode if explicitly set */
239
1.04k
  const char *response_mode = oidc_proto_state_get_response_mode(proto_state);
240
1.04k
  if (response_mode != NULL)
241
0
    apr_table_setn(params, OIDC_PROTO_RESPONSE_MODE, response_mode);
242
243
  /* add the login_hint if provided */
244
1.04k
  if (login_hint != NULL)
245
176
    apr_table_setn(params, OIDC_PROTO_LOGIN_HINT, login_hint);
246
247
  /* add the id_token_hint if provided */
248
1.04k
  if (id_token_hint != NULL)
249
5
    apr_table_setn(params, OIDC_PROTO_ID_TOKEN_HINT, id_token_hint);
250
251
  /* add the prompt setting if provided (e.g. "none" for no-GUI checks) */
252
1.04k
  const char *prompt = oidc_proto_state_get_prompt(proto_state);
253
1.04k
  if (prompt != NULL)
254
9
    apr_table_setn(params, OIDC_PROTO_PROMPT, prompt);
255
256
  /* the keys the two directives below add, shared between their invocations so that a
257
   * parameter the first one added is not mistaken for one this module owns by the second */
258
1.04k
  apr_table_t *added = apr_table_make(r->pool, 4);
259
260
  /* add any statically configured custom authorization request parameters */
261
1.04k
  oidc_proto_request_auth_params_add(r, params, added, oidc_cfg_provider_auth_request_params_get(provider), TRUE);
262
263
  /* add any dynamically configured custom authorization request parameters */
264
1.04k
  oidc_proto_request_auth_params_add(r, params, added, auth_request_params, FALSE);
265
266
  /* add request parameter (request or request_uri) if set */
267
1.04k
  if (oidc_cfg_provider_request_object_get(provider) != NULL)
268
0
    oidc_proto_request_object_param_add(r, cfg, provider, redirect_uri, params);
269
1.04k
}