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