/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 | } |