/src/mod_auth_openidc/src/handle/userinfo.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 "cfg/provider.h" |
45 | | #include "handle/handle.h" |
46 | | #include "mod_auth_openidc.h" |
47 | | #include "proto/proto.h" |
48 | | #include "util/util.h" |
49 | | #include "util/util_cfg.h" |
50 | | |
51 | | /* |
52 | | * store claims resolved from the userinfo endpoint in the session |
53 | | */ |
54 | | void oidc_userinfo_store_claims(request_rec *r, const oidc_cfg_t *c, oidc_session_t *session, |
55 | | const oidc_provider_t *provider, oidc_json_t *userinfo_claims, |
56 | 416 | const char *userinfo_jwt) { |
57 | | |
58 | 416 | oidc_debug(r, "enter"); |
59 | | |
60 | | /* see if we've resolved any claims */ |
61 | 416 | if (userinfo_claims != NULL) { |
62 | | /* |
63 | | * Successfully decoded a set claims from the response so we can store them |
64 | | * (well actually the stringified representation in the response) |
65 | | * in the session context safely now |
66 | | */ |
67 | 0 | oidc_session_set_userinfo_claims(r, session, userinfo_claims); |
68 | |
|
69 | 0 | if (oidc_cfg_session_type_get(c) != OIDC_SESSION_TYPE_CLIENT_COOKIE) { |
70 | | /* this will also clear the entry if a JWT was not returned at this point */ |
71 | 0 | oidc_session_set_userinfo_jwt(r, session, userinfo_jwt); |
72 | 0 | } |
73 | |
|
74 | 416 | } else { |
75 | | /* |
76 | | * clear the existing claims because we could not refresh them |
77 | | */ |
78 | 416 | oidc_session_set_userinfo_claims(r, session, NULL); |
79 | | |
80 | 416 | oidc_session_set_userinfo_jwt(r, session, NULL); |
81 | 416 | } |
82 | | |
83 | | /* store the last refresh time if we've configured a userinfo refresh interval */ |
84 | 416 | if (oidc_cfg_provider_userinfo_refresh_interval_get(provider) > -1) |
85 | 0 | oidc_session_reset_userinfo_last_refresh(r, session); |
86 | 416 | } |
87 | | |
88 | | /* |
89 | | * retrieve claims from the userinfo endpoint and return the stringified response |
90 | | */ |
91 | | const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, const oidc_provider_t *provider, |
92 | | const char *access_token, const char *access_token_type, |
93 | | oidc_session_t *session, char *id_token_sub, oidc_json_t **userinfo_claims, |
94 | 416 | char **userinfo_jwt) { |
95 | | |
96 | 416 | char *result = NULL; |
97 | 416 | char *refreshed_access_token = NULL; |
98 | 416 | char *refreshed_access_token_type = NULL; |
99 | 416 | const oidc_json_t *id_token_claims = NULL; |
100 | 416 | long response_code = 0; |
101 | | |
102 | 416 | oidc_debug(r, "enter"); |
103 | | |
104 | | /* see if a userinfo endpoint is set (and not explicitly disabled with an empty value), otherwise there's |
105 | | * nothing to do for us */ |
106 | 416 | if ((oidc_cfg_provider_userinfo_endpoint_url_get(provider) == NULL) || |
107 | 416 | (_oidc_strcmp(oidc_cfg_provider_userinfo_endpoint_url_get(provider), "") == 0)) { |
108 | 416 | oidc_debug(r, "not retrieving userinfo claims because userinfo_endpoint is not set or disabled"); |
109 | 416 | goto end; |
110 | 416 | } |
111 | | |
112 | | /* see if there's an access token, otherwise we can't call the userinfo endpoint at all */ |
113 | 0 | if (access_token == NULL) { |
114 | 0 | oidc_debug(r, "not retrieving userinfo claims because access_token is not provided"); |
115 | 0 | goto end; |
116 | 0 | } |
117 | | |
118 | 0 | if ((id_token_sub == NULL) && (session != NULL)) { |
119 | | // when refreshing claims from the userinfo endpoint |
120 | 0 | id_token_claims = oidc_session_get_idtoken_claims(r, session); |
121 | 0 | if (id_token_claims != NULL) { |
122 | 0 | oidc_jose_get_string(r->pool, id_token_claims, OIDC_CLAIM_SUB, FALSE, &id_token_sub, NULL); |
123 | 0 | } else { |
124 | 0 | oidc_debug(r, "no id_token_claims found in session"); |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | | /* |
129 | | * NB: userinfo results are ultimately stored in the session in their stringified form; a future |
130 | | * improvement would be to keep session storage as typed JSON to avoid the string<->JSON conversions |
131 | | */ |
132 | | |
133 | | /* try to get claims from the userinfo endpoint using the provided access token */ |
134 | 0 | if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, access_token, access_token_type, &result, |
135 | 0 | userinfo_jwt, userinfo_claims, &response_code) == TRUE) |
136 | 0 | goto end; |
137 | | |
138 | | /* see if this is the initial call to the user info endpoint upon receiving the authorization response */ |
139 | 0 | if (session == NULL) { |
140 | 0 | oidc_error(r, "resolving user info claims with the provided access token failed, nothing will be " |
141 | 0 | "stored in the session"); |
142 | 0 | result = NULL; |
143 | 0 | goto end; |
144 | 0 | } |
145 | | |
146 | | // a connectivity error rather than a HTTP error: refreshing the access token won't help |
147 | 0 | if (response_code == 0) { |
148 | 0 | oidc_error(r, "resolving user info claims failed with a connectivity error, no attempt will be made to " |
149 | 0 | "refresh the access token and try again"); |
150 | 0 | result = NULL; |
151 | 0 | goto end; |
152 | 0 | } |
153 | | |
154 | | /* only an "invalid_token" response (401) indicates the access token may have expired and is worth |
155 | | * refreshing; any other HTTP error (e.g. 403, 5xx) will not be resolved by presenting a fresh token */ |
156 | 0 | if (response_code != HTTP_UNAUTHORIZED) { |
157 | 0 | oidc_error(r, |
158 | 0 | "resolving user info claims failed with HTTP status %ld; since this is not a 401 the access " |
159 | 0 | "token will not be refreshed", |
160 | 0 | response_code); |
161 | 0 | result = NULL; |
162 | 0 | goto end; |
163 | 0 | } |
164 | | |
165 | | /* first call to user info endpoint failed, but this is for an existing session and the access token may have |
166 | | * just expired, so refresh it */ |
167 | 0 | if (oidc_refresh_token_grant(r, c, session, provider, &refreshed_access_token, &refreshed_access_token_type, |
168 | 0 | NULL) == FALSE) { |
169 | 0 | oidc_error(r, "refreshing access token failed, claims will not be retrieved/refreshed from the " |
170 | 0 | "userinfo endpoint"); |
171 | 0 | result = NULL; |
172 | 0 | goto end; |
173 | 0 | } |
174 | | |
175 | | /* try again with the new access token */ |
176 | 0 | if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, refreshed_access_token, |
177 | 0 | refreshed_access_token_type, &result, userinfo_jwt, userinfo_claims, |
178 | 0 | NULL) == FALSE) { |
179 | |
|
180 | 0 | oidc_error(r, "resolving user info claims with the refreshed access token failed, nothing will be " |
181 | 0 | "stored in the session"); |
182 | 0 | result = NULL; |
183 | 0 | goto end; |
184 | 0 | } |
185 | | |
186 | 416 | end: |
187 | | |
188 | 416 | oidc_debug(r, "return (%d)", result != NULL); |
189 | | |
190 | 416 | return result; |
191 | 0 | } |
192 | | |
193 | | /* |
194 | | * get (new) claims from the userinfo endpoint |
195 | | */ |
196 | | apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, |
197 | 0 | apr_byte_t *needs_save) { |
198 | |
|
199 | 0 | apr_byte_t rc = TRUE; |
200 | 0 | oidc_provider_t *provider = NULL; |
201 | 0 | const char *claims = NULL; |
202 | 0 | char *userinfo_jwt = NULL; |
203 | 0 | oidc_json_t *userinfo_claims = NULL; |
204 | 0 | apr_time_t last_refresh = 0; |
205 | | |
206 | | /* see if we can do anything here, i.e. a refresh interval is configured */ |
207 | 0 | int interval = oidc_session_get_userinfo_refresh_interval(r, session); |
208 | |
|
209 | 0 | oidc_debug(r, "interval=%d", interval); |
210 | |
|
211 | 0 | if (interval <= -1) |
212 | 0 | goto end; |
213 | | |
214 | | /* get the current provider info */ |
215 | 0 | if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) { |
216 | 0 | *needs_save = TRUE; |
217 | 0 | rc = FALSE; |
218 | 0 | goto end; |
219 | 0 | } |
220 | | |
221 | | /* nothing to do without a userinfo endpoint (or when it is explicitly disabled with an empty value) */ |
222 | 0 | if ((oidc_cfg_provider_userinfo_endpoint_url_get(provider) == NULL) || |
223 | 0 | (_oidc_strcmp(oidc_cfg_provider_userinfo_endpoint_url_get(provider), "") == 0)) |
224 | 0 | goto end; |
225 | | |
226 | | /* get the last refresh timestamp from the session info */ |
227 | 0 | last_refresh = oidc_session_get_userinfo_last_refresh(r, session); |
228 | |
|
229 | 0 | oidc_debug(r, |
230 | 0 | "refresh needed in: %" APR_TIME_T_FMT " seconds (last_refresh=%" APR_TIME_T_FMT |
231 | 0 | ", interval=%d, now=%" APR_TIME_T_FMT ")", |
232 | 0 | apr_time_sec(last_refresh + apr_time_from_sec(interval) - apr_time_now()), |
233 | 0 | apr_time_sec(last_refresh), interval, apr_time_sec(apr_time_now())); |
234 | | |
235 | | /* see if we need to refresh again */ |
236 | 0 | if (last_refresh + apr_time_from_sec(interval) >= apr_time_now()) |
237 | 0 | goto end; |
238 | | |
239 | | /* retrieve the current claims using the current access token */ |
240 | 0 | claims = oidc_userinfo_retrieve_claims(r, cfg, provider, oidc_session_get_access_token(r, session), |
241 | 0 | oidc_session_get_access_token_type(r, session), session, NULL, |
242 | 0 | &userinfo_claims, &userinfo_jwt); |
243 | | |
244 | | /* store claims resolved from userinfo endpoint */ |
245 | 0 | oidc_userinfo_store_claims(r, cfg, session, provider, userinfo_claims, userinfo_jwt); |
246 | |
|
247 | 0 | oidc_json_decref(userinfo_claims); |
248 | |
|
249 | 0 | if (claims == NULL) { |
250 | 0 | *needs_save = FALSE; |
251 | 0 | rc = FALSE; |
252 | 0 | } else { |
253 | | /* indicated something changed */ |
254 | 0 | *needs_save = TRUE; |
255 | 0 | } |
256 | |
|
257 | 0 | end: |
258 | |
|
259 | 0 | oidc_debug(r, "return: %d", rc); |
260 | |
|
261 | 0 | return rc; |
262 | 0 | } |
263 | | |
264 | 0 | #define OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT 60 |
265 | 0 | #define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT -1 |
266 | 0 | #define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL" |
267 | | |
268 | | /* |
269 | | * obtain the signed JWT cache TTL from the environment variables |
270 | | */ |
271 | 0 | static int oidc_userinfo_signed_jwt_cache_ttl(const request_rec *r) { |
272 | 0 | const char *s_ttl = apr_table_get(r->subprocess_env, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR); |
273 | 0 | return _oidc_str_to_int(s_ttl, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT); |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * create a signed JWT with s_claims payload and return the serialized form in cser |
278 | | */ |
279 | | static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cfg, const oidc_session_t *session, |
280 | 0 | oidc_json_t *claims, char **cser) { |
281 | 0 | apr_byte_t rv = FALSE; |
282 | 0 | oidc_jwt_t *jwt = NULL; |
283 | 0 | oidc_jwk_t *jwk = NULL; |
284 | 0 | oidc_jose_error_t err; |
285 | 0 | apr_time_t access_token_expires = -1; |
286 | 0 | char *key = NULL; |
287 | 0 | int ttl = 0; |
288 | 0 | int exp = 0; |
289 | 0 | apr_time_t expiry = 0; |
290 | |
|
291 | 0 | if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, FALSE) == FALSE) |
292 | 0 | goto end; |
293 | | |
294 | 0 | oidc_json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, |
295 | 0 | oidc_json_string(oidc_util_url_cur(r, oidc_cfg_x_forwarded_headers_get(cfg)))); |
296 | 0 | oidc_json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, |
297 | 0 | oidc_json_string(oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)))); |
298 | |
|
299 | 0 | if (oidc_json_merge(r, claims, jwt->payload.value.json) == FALSE) |
300 | 0 | goto end; |
301 | 0 | const char *s_claims = |
302 | 0 | oidc_json_encode(r->pool, jwt->payload.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT); |
303 | 0 | if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, s_claims, |
304 | 0 | (int)_oidc_strlen(s_claims) + 1, &key, &err) == FALSE) { |
305 | 0 | oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); |
306 | 0 | goto end; |
307 | 0 | } |
308 | | |
309 | 0 | ttl = oidc_userinfo_signed_jwt_cache_ttl(r); |
310 | 0 | if (ttl > -1) |
311 | 0 | oidc_cache_get_signed_jwt(r, key, cser); |
312 | |
|
313 | 0 | if (*cser != NULL) { |
314 | 0 | oidc_debug(r, "signed JWT found in cache"); |
315 | 0 | rv = TRUE; |
316 | 0 | goto end; |
317 | 0 | } |
318 | | |
319 | 0 | if (oidc_json_object_get(jwt->payload.value.json, OIDC_CLAIM_JTI) == NULL) { |
320 | 0 | oidc_json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, |
321 | 0 | oidc_json_string(oidc_proto_jti_gen(r))); |
322 | 0 | } |
323 | 0 | if (oidc_json_object_get(jwt->payload.value.json, OIDC_CLAIM_IAT) == NULL) { |
324 | 0 | oidc_json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, |
325 | 0 | oidc_json_integer(apr_time_sec(apr_time_now()))); |
326 | 0 | } |
327 | 0 | if (oidc_json_object_get(jwt->payload.value.json, OIDC_CLAIM_EXP) == NULL) { |
328 | 0 | access_token_expires = oidc_session_get_access_token_expires(r, session); |
329 | 0 | oidc_json_object_set_new( |
330 | 0 | jwt->payload.value.json, OIDC_CLAIM_EXP, |
331 | 0 | oidc_json_integer(access_token_expires > 0 |
332 | 0 | ? apr_time_sec(access_token_expires) |
333 | 0 | : apr_time_sec(apr_time_now()) + OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT)); |
334 | 0 | } |
335 | |
|
336 | 0 | if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, cser) == FALSE) |
337 | 0 | goto end; |
338 | | |
339 | 0 | rv = TRUE; |
340 | |
|
341 | 0 | if (ttl < 0) |
342 | 0 | goto end; |
343 | | |
344 | 0 | if (ttl == 0) { |
345 | | // need to get the cache ttl from the exp claim |
346 | 0 | oidc_json_object_get_int(jwt->payload.value.json, OIDC_CLAIM_EXP, &exp, 0); |
347 | | // actually the exp claim always exists by now |
348 | 0 | expiry = (exp > 0) ? apr_time_from_sec(exp) |
349 | 0 | : apr_time_now() + apr_time_from_sec(OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT); |
350 | 0 | } else { |
351 | | // ttl > 0 |
352 | 0 | expiry = apr_time_now() + apr_time_from_sec(ttl); |
353 | 0 | } |
354 | |
|
355 | 0 | oidc_debug(r, "caching signed JWT with ~ttl(%ld)", apr_time_sec(expiry - apr_time_now())); |
356 | 0 | oidc_cache_set_signed_jwt(r, key, *cser, expiry); |
357 | |
|
358 | 0 | end: |
359 | |
|
360 | 0 | if (jwt) |
361 | 0 | oidc_jwt_destroy(jwt); |
362 | |
|
363 | 0 | return rv; |
364 | 0 | } |
365 | | |
366 | | /* |
367 | | * set an appinfo value, falling back to default_name (with the default |
368 | | * header prefix) when no explicit name was configured |
369 | | */ |
370 | | static void oidc_userinfo_appinfo_set(request_rec *r, const char *name, const char *default_name, const char *value, |
371 | 0 | oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { |
372 | 0 | oidc_util_appinfo_set(r, name ? name : default_name, value, name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, |
373 | 0 | encoding); |
374 | 0 | } |
375 | | |
376 | | /* |
377 | | * pass the userinfo JWT stored in the session to the app, rejecting the |
378 | | * client-cookie session type which does not support storing such JWTs |
379 | | */ |
380 | | static void oidc_userinfo_pass_as_jwt(request_rec *r, const oidc_cfg_t *cfg, const oidc_session_t *session, |
381 | | const char *name, oidc_appinfo_pass_in_t pass_in, |
382 | 0 | oidc_appinfo_encoding_t encoding) { |
383 | |
|
384 | 0 | if (oidc_cfg_session_type_get(cfg) == OIDC_SESSION_TYPE_CLIENT_COOKIE) { |
385 | 0 | oidc_error(r, "session type \"client-cookie\" does not allow storing/passing a " |
386 | 0 | "userinfo JWT; use \"" OIDCSessionType " server-cache\" for that"); |
387 | 0 | return; |
388 | 0 | } |
389 | | |
390 | 0 | const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r, session); |
391 | 0 | if (s_userinfo_jwt == NULL) { |
392 | 0 | oidc_debug(r, "configured to pass userinfo in a JWT, but no such JWT was found in the session " |
393 | 0 | "(probably no such JWT was returned from the userinfo endpoint)"); |
394 | 0 | return; |
395 | 0 | } |
396 | | |
397 | 0 | oidc_userinfo_appinfo_set(r, name, OIDC_APP_INFO_USERINFO_JWT, s_userinfo_jwt, pass_in, encoding); |
398 | 0 | } |
399 | | |
400 | | /* |
401 | | * dispatch a single configured "pass userinfo as" entry to the matching |
402 | | * encoding-specific handler |
403 | | */ |
404 | | static void oidc_userinfo_pass_entry(request_rec *r, oidc_cfg_t *cfg, const oidc_session_t *session, |
405 | | oidc_json_t *claims, const oidc_pass_user_info_as_t *p, |
406 | 0 | oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { |
407 | |
|
408 | 0 | char *cser = NULL; |
409 | |
|
410 | 0 | switch (p->type) { |
411 | 0 | case OIDC_PASS_USERINFO_AS_CLAIMS: |
412 | | /* set the userinfo claims in the app headers */ |
413 | 0 | oidc_set_app_claims(r, cfg, claims); |
414 | 0 | break; |
415 | 0 | case OIDC_PASS_USERINFO_AS_JSON_OBJECT: |
416 | | /* pass the userinfo JSON object to the app in a header or environment variable */ |
417 | 0 | oidc_userinfo_appinfo_set( |
418 | 0 | r, p->name, OIDC_APP_INFO_USERINFO_JSON, |
419 | 0 | oidc_json_encode(r->pool, claims, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT), pass_in, encoding); |
420 | 0 | break; |
421 | 0 | case OIDC_PASS_USERINFO_AS_JWT: |
422 | 0 | oidc_userinfo_pass_as_jwt(r, cfg, session, p->name, pass_in, encoding); |
423 | 0 | break; |
424 | 0 | case OIDC_PASS_USERINFO_AS_SIGNED_JWT: |
425 | 0 | if (oidc_userinfo_create_signed_jwt(r, cfg, session, claims, &cser) == TRUE) |
426 | 0 | oidc_userinfo_appinfo_set(r, p->name, OIDC_APP_INFO_SIGNED_JWT, cser, pass_in, encoding); |
427 | 0 | break; |
428 | 0 | default: |
429 | 0 | break; |
430 | 0 | } |
431 | 0 | } |
432 | | |
433 | | /* |
434 | | * resolve the claims to pass to the app, optionally applying a JQ filter |
435 | | * to the userinfo claims stored in the session; *filtered receives the |
436 | | * newly allocated oidc_json_t that the caller must release, or NULL when no |
437 | | * filter was applied |
438 | | */ |
439 | | static oidc_json_t *oidc_userinfo_resolve_claims(request_rec *r, const oidc_session_t *session, |
440 | 0 | oidc_json_t **filtered) { |
441 | |
|
442 | 0 | *filtered = NULL; |
443 | |
|
444 | | #ifdef USE_LIBJQ |
445 | | const char *s_filter = oidc_cfg_dir_userinfo_claims_expr_get(r); |
446 | | if (s_filter != NULL) { |
447 | | const char *s_claims = oidc_util_jq_filter(r, oidc_session_get_userinfo_claims(r, session), s_filter); |
448 | | if (oidc_json_decode_object(r, s_claims, filtered) == FALSE) { |
449 | | oidc_error(r, "JQ filtering of claims for [%s] resulted in invalid JSON object, filter='%s'", |
450 | | "userinfo", s_filter); |
451 | | return NULL; |
452 | | } |
453 | | return *filtered; |
454 | | } |
455 | | #endif |
456 | |
|
457 | 0 | return oidc_session_get_userinfo_claims(r, session); |
458 | 0 | } |
459 | | |
460 | | /* |
461 | | * pass the userinfo claims to headers and/or environment variables, encoded as configured |
462 | | */ |
463 | | void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, const oidc_session_t *session, |
464 | 0 | oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { |
465 | |
|
466 | 0 | const apr_array_header_t *pass_userinfo_as = oidc_cfg_dir_pass_userinfo_as_get(r); |
467 | 0 | oidc_json_t *filtered_claims = NULL; |
468 | 0 | oidc_json_t *claims = oidc_userinfo_resolve_claims(r, session, &filtered_claims); |
469 | |
|
470 | 0 | if (claims == NULL) |
471 | 0 | return; |
472 | | |
473 | 0 | for (int i = 0; (pass_userinfo_as != NULL) && (i < pass_userinfo_as->nelts); i++) |
474 | 0 | oidc_userinfo_pass_entry(r, cfg, session, claims, |
475 | 0 | APR_ARRAY_IDX(pass_userinfo_as, i, oidc_pass_user_info_as_t *), pass_in, |
476 | 0 | encoding); |
477 | |
|
478 | 0 | if (filtered_claims) |
479 | 0 | oidc_json_decref(filtered_claims); |
480 | 0 | } |