Coverage Report

Created: 2026-08-31 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/handle/info.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 "handle/handle.h"
44
#include "mod_auth_openidc.h"
45
#include "util/util.h"
46
47
101
#define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval"
48
49
101
#define OIDC_HOOK_INFO_FORMAT_JSON "json"
50
98
#define OIDC_HOOK_INFO_FORMAT_HTML "html"
51
52
/*
53
 * see if we can and need to refresh the access token
54
 */
55
static int oidc_info_refresh_access_token(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
56
0
            const char *s_interval, apr_byte_t *needs_save) {
57
0
  apr_time_t t_interval = -1;
58
0
  apr_time_t last_refresh = 0;
59
0
  oidc_provider_t *provider = NULL;
60
61
0
  if ((s_interval == NULL) || (oidc_session_get_refresh_token(r, session) == NULL))
62
0
    return OK;
63
64
0
  t_interval = _oidc_str_to_time(s_interval, -1);
65
0
  if (t_interval <= -1)
66
0
    return OK;
67
68
0
  t_interval = apr_time_from_sec(t_interval);
69
0
  last_refresh = oidc_session_get_access_token_last_refresh(r, session);
70
0
  oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
71
0
       apr_time_sec(last_refresh + t_interval - apr_time_now()));
72
73
0
  if (last_refresh + t_interval >= apr_time_now())
74
0
    return OK;
75
76
0
  if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE)
77
0
    return HTTP_INTERNAL_SERVER_ERROR;
78
79
0
  if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, NULL) == FALSE) {
80
0
    oidc_warn(r, "access_token could not be refreshed");
81
0
    return HTTP_INTERNAL_SERVER_ERROR;
82
0
  }
83
84
0
  *needs_save = TRUE;
85
0
  return OK;
86
0
}
87
88
/*
89
 * include the access token and its type in the session info
90
 */
91
0
static void oidc_info_add_access_token(request_rec *r, const oidc_session_t *session, oidc_json_t *json) {
92
0
  const char *access_token = oidc_session_get_access_token(r, session);
93
0
  if (access_token != NULL)
94
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN, oidc_json_string(access_token));
95
0
  const char *access_token_type = oidc_session_get_access_token_type(r, session);
96
0
  if (access_token_type != NULL)
97
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_TYPE, oidc_json_string(access_token_type));
98
0
}
99
100
/*
101
 * include the session state and uuid in the session info
102
 */
103
0
static void oidc_info_add_session(oidc_session_t *session, oidc_json_t *json) {
104
0
  oidc_json_t *j_session = oidc_json_object();
105
0
  oidc_json_object_set(j_session, OIDC_HOOK_INFO_SESSION_STATE, session->state);
106
0
  oidc_json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_UUID, oidc_json_string(session->uuid));
107
0
  oidc_json_object_set_new(json, OIDC_HOOK_INFO_SESSION, j_session);
108
0
}
109
110
/*
111
 * build the JSON object that is returned to the caller based on the configured info hook data
112
 */
113
0
static void oidc_info_build_json(request_rec *r, const oidc_cfg_t *c, oidc_session_t *session, oidc_json_t *json) {
114
0
  apr_hash_t *data = oidc_cfg_info_hook_data_get(c);
115
116
  /* add a timestamp of creation in there for the caller */
117
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_TIMESTAMP, APR_HASH_KEY_STRING))
118
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_TIMESTAMP,
119
0
           oidc_json_integer(apr_time_sec(apr_time_now())));
120
121
  /* include the access token in the session info */
122
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_ACCES_TOKEN, APR_HASH_KEY_STRING))
123
0
    oidc_info_add_access_token(r, session, json);
124
125
  /* include the access token expiry timestamp in the session info */
126
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, APR_HASH_KEY_STRING)) {
127
0
    const char *access_token_expires = oidc_session_get_access_token_expires2str(r, session);
128
0
    if (access_token_expires != NULL)
129
0
      oidc_json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
130
0
             oidc_json_string(access_token_expires));
131
0
  }
132
133
  /* include the serialized id_token (id_token_hint) in the session info */
134
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_ID_TOKEN_HINT, APR_HASH_KEY_STRING)) {
135
0
    const char *s_id_token = oidc_session_get_idtoken(r, session);
136
0
    if (s_id_token != NULL)
137
0
      oidc_json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN_HINT, oidc_json_string(s_id_token));
138
0
  }
139
140
  /* include the id_token claims in the session info */
141
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_ID_TOKEN, APR_HASH_KEY_STRING)) {
142
0
    oidc_json_t *id_token = oidc_session_get_idtoken_claims(r, session);
143
0
    if (id_token)
144
0
      oidc_json_object_set(json, OIDC_HOOK_INFO_ID_TOKEN, id_token);
145
0
  }
146
147
  /* include the claims from the userinfo endpoint in the session info */
148
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_USER_INFO, APR_HASH_KEY_STRING)) {
149
0
    oidc_json_t *claims = oidc_session_get_userinfo_claims(r, session);
150
0
    if (claims)
151
0
      oidc_json_object_set(json, OIDC_HOOK_INFO_USER_INFO, claims);
152
0
  }
153
154
  /* include the maximum session lifetime in the session info */
155
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_SESSION_EXP, APR_HASH_KEY_STRING)) {
156
0
    apr_time_t session_expires = oidc_session_get_session_expires(r, session);
157
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_SESSION_EXP,
158
0
           oidc_json_integer(apr_time_sec(session_expires)));
159
0
  }
160
161
  /* include the inactivity timeout in the session info */
162
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_SESSION_TIMEOUT, APR_HASH_KEY_STRING))
163
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_SESSION_TIMEOUT,
164
0
           oidc_json_integer(apr_time_sec(session->expiry)));
165
166
  /* include the remote_user in the session info */
167
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_SESSION_REMOTE_USER, APR_HASH_KEY_STRING))
168
0
    oidc_json_object_set_new(json, OIDC_HOOK_INFO_SESSION_REMOTE_USER,
169
0
           oidc_json_string(session->remote_user));
170
171
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_SESSION, APR_HASH_KEY_STRING))
172
0
    oidc_info_add_session(session, json);
173
174
  /* include the refresh token in the session info */
175
0
  if (apr_hash_get(data, OIDC_HOOK_INFO_REFRESH_TOKEN, APR_HASH_KEY_STRING)) {
176
0
    const char *refresh_token = oidc_session_get_refresh_token(r, session);
177
0
    if (refresh_token != NULL)
178
0
      oidc_json_object_set_new(json, OIDC_HOOK_INFO_REFRESH_TOKEN, oidc_json_string(refresh_token));
179
0
  }
180
0
}
181
182
/*
183
 * send the session info to the caller in the requested format (JSON or HTML)
184
 */
185
0
static int oidc_info_send_response(request_rec *r, const oidc_json_t *json, const char *s_format) {
186
0
  const char *r_value = NULL;
187
188
  /* the response may carry the access/refresh/id token and session claims; prevent it from being
189
   * stored by the browser or any intermediary cache */
190
0
  oidc_http_set_no_cache_headers(r);
191
192
0
  if (_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) == 0) {
193
0
    r_value = oidc_json_encode(r->pool, json, OIDC_JSON_PRESERVE_ORDER);
194
0
    return oidc_util_http_send(r, r_value, _oidc_strlen(r_value), OIDC_HTTP_CONTENT_TYPE_JSON, OK);
195
0
  }
196
197
0
  r_value = oidc_json_encode(r->pool, json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_INDENT(2));
198
0
  return oidc_util_html_send(r, "Session Info", NULL, NULL,
199
0
           apr_psprintf(r->pool, "<pre>%s</pre>", oidc_util_html_escape(r->pool, r_value)), OK);
200
0
}
201
202
/*
203
 * handle request for session info
204
 */
205
101
int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_byte_t needs_save) {
206
101
  int rc = HTTP_UNAUTHORIZED;
207
101
  char *s_format = NULL;
208
101
  char *s_interval = NULL;
209
101
  char *s_extend_session = NULL;
210
101
  apr_byte_t b_extend_session = TRUE;
211
101
  oidc_json_t *json = NULL;
212
213
101
  oidc_util_url_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_INFO, &s_format);
214
101
  oidc_util_url_parameter_get(r, OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval);
215
101
  oidc_util_url_parameter_get(r, OIDC_INFO_PARAM_EXTEND_SESSION, &s_extend_session);
216
101
  if (s_extend_session && (_oidc_strcmp(s_extend_session, "false") == 0))
217
1
    b_extend_session = FALSE;
218
219
  /* see if this is a request for a format that is supported */
220
101
  if ((_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) != 0) &&
221
98
      (_oidc_strcmp(OIDC_HOOK_INFO_FORMAT_HTML, s_format) != 0)) {
222
97
    oidc_warn(r, "request for unknown format: %s", s_format);
223
97
    return HTTP_UNSUPPORTED_MEDIA_TYPE;
224
97
  }
225
226
  /* check that we actually have a user session and this is someone calling with a proper session cookie */
227
4
  if (session->remote_user == NULL) {
228
4
    oidc_warn(r, "no user session found");
229
4
    return HTTP_UNAUTHORIZED;
230
4
  }
231
232
  /* set the user in the main request for further (incl. sub-request and authz) processing */
233
0
  r->user = apr_pstrdup(r->pool, session->remote_user);
234
235
0
  if (oidc_cfg_info_hook_data_get(c) == NULL) {
236
0
    oidc_warn(r, "no data configured to return in " OIDCInfoHook);
237
0
    return HTTP_NOT_FOUND;
238
0
  }
239
240
0
  rc = oidc_info_refresh_access_token(r, c, session, s_interval, &needs_save);
241
0
  if (rc != OK)
242
0
    return rc;
243
244
  /* create the JSON object */
245
0
  json = oidc_json_object();
246
247
  /*
248
   * refresh the claims from the userinfo endpoint
249
   * side-effect is that this may refresh the access token if not already done
250
   * note that OIDCUserInfoRefreshInterval should be set to control the refresh policy
251
   */
252
0
  if (b_extend_session && (oidc_userinfo_refresh_claims(r, c, session, &needs_save) == FALSE)) {
253
0
    rc = HTTP_INTERNAL_SERVER_ERROR;
254
0
    goto end;
255
0
  }
256
257
0
  oidc_info_build_json(r, c, session, json);
258
259
  /* pass the tokens to the application and save the session, possibly updating the expiry */
260
0
  oidc_session_pass_tokens(r, c, session, b_extend_session, &needs_save);
261
262
  /* check if something was updated in the session and we need to save it again */
263
0
  if (b_extend_session && needs_save && (oidc_session_save(r, session, OIDC_SESSION_SAVE_UPDATE) == FALSE)) {
264
0
    oidc_warn(r, "error saving session");
265
0
    rc = HTTP_INTERNAL_SERVER_ERROR;
266
0
    goto end;
267
0
  }
268
269
0
  rc = oidc_info_send_response(r, json, s_format);
270
271
0
end:
272
273
  /* free the allocated resources */
274
0
  oidc_json_decref(json);
275
276
0
  return rc;
277
0
}