Coverage Report

Created: 2026-09-01 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/proto/discovery.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/parse.h"
45
#include "proto/proto.h"
46
#include "util/util.h"
47
48
/*
49
 * parse a webfinger response body and extract the issuer href.
50
 *
51
 * Non-static so unit tests can exercise the JSON validation paths directly without
52
 * needing a live HTTPS webfinger endpoint; not part of the public proto.h API.
53
 */
54
0
apr_byte_t oidc_proto_webfinger_response_get_issuer(request_rec *r, const char *response, char **issuer) {
55
56
  /* decode and see if it is not an error response somehow */
57
0
  oidc_json_t *j_response = NULL;
58
0
  if (oidc_json_decode_and_check_error(r, response, &j_response) == FALSE)
59
0
    return FALSE;
60
61
0
  apr_byte_t rv = FALSE;
62
63
  /* get the links parameter */
64
0
  const oidc_json_t *j_links = oidc_json_object_get(j_response, "links");
65
0
  if ((j_links == NULL) || (!oidc_json_is_array(j_links))) {
66
0
    oidc_error(r, "response JSON object did not contain a \"links\" array");
67
0
    goto end;
68
0
  }
69
70
  /* get the one-and-only object in the "links" array */
71
0
  const oidc_json_t *j_object = oidc_json_array_get(j_links, 0);
72
0
  if ((j_object == NULL) || (!oidc_json_is_object(j_object))) {
73
0
    oidc_error(
74
0
        r,
75
0
        "response JSON object did not contain a JSON object as the first element in the \"links\" array");
76
0
    goto end;
77
0
  }
78
79
  /* get the href from that object, which is the issuer value */
80
0
  const oidc_json_t *j_href = oidc_json_object_get(j_object, "href");
81
0
  if ((j_href == NULL) || (!oidc_json_is_string(j_href))) {
82
0
    oidc_error(
83
0
        r, "response JSON object did not contain a \"href\" element in the first \"links\" array object");
84
0
    goto end;
85
0
  }
86
87
  /* check that the link is on secure HTTPs */
88
0
  if (oidc_cfg_parse_is_valid_url(r->pool, oidc_json_string_value(j_href), "https") != NULL) {
89
0
    oidc_error(r, "response JSON object contains an \"href\" value that is not a valid \"https\" URL: %s",
90
0
         oidc_json_string_value(j_href));
91
0
    goto end;
92
0
  }
93
94
0
  *issuer = apr_pstrdup(r->pool, oidc_json_string_value(j_href));
95
0
  rv = TRUE;
96
97
0
end:
98
0
  oidc_json_decref(j_response);
99
0
  return rv;
100
0
}
101
102
/*
103
 * based on a resource perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its
104
 * metadata
105
 */
106
static apr_byte_t oidc_proto_webfinger_discovery(request_rec *r, oidc_cfg_t *cfg, const char *resource,
107
0
             const char *domain, char **issuer) {
108
109
0
  const char *url = apr_psprintf(r->pool, "https://%s/.well-known/webfinger", domain);
110
111
0
  apr_table_t *params = apr_table_make(r->pool, 1);
112
0
  apr_table_setn(params, "resource", resource);
113
0
  apr_table_setn(params, "rel", "http://openid.net/specs/connect/1.0/issuer");
114
115
0
  char *response = NULL;
116
0
  if (oidc_http_get(r, url, params, NULL, NULL, NULL,
117
0
        oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), &response, NULL, NULL,
118
0
        oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg),
119
0
        oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) {
120
    /* errors will have been logged by now */
121
0
    return FALSE;
122
0
  }
123
124
0
  if (oidc_proto_webfinger_response_get_issuer(r, response, issuer) == FALSE)
125
0
    return FALSE;
126
127
0
  oidc_debug(r, "returning issuer \"%s\" for resource \"%s\" after doing successful webfinger-based discovery",
128
0
       *issuer, resource);
129
130
0
  return TRUE;
131
0
}
132
133
/*
134
 * based on an account name, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and
135
 * store its metadata
136
 */
137
0
apr_byte_t oidc_proto_discovery_account_based(request_rec *r, oidc_cfg_t *cfg, const char *acct, char **issuer) {
138
139
0
  oidc_debug(r, "enter, acct=%s", acct);
140
141
0
  const char *resource = apr_psprintf(r->pool, "acct:%s", acct);
142
0
  const char *domain = strrchr(acct, OIDC_CHAR_AT);
143
0
  if (domain == NULL) {
144
0
    oidc_error(r, "invalid account name");
145
0
    return FALSE;
146
0
  }
147
0
  domain++;
148
149
0
  return oidc_proto_webfinger_discovery(r, cfg, resource, domain, issuer);
150
0
}
151
152
/*
153
 * based on user identifier URL, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and
154
 * store its metadata
155
 */
156
0
apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const char *url, char **issuer) {
157
158
0
  oidc_debug(r, "enter, url=%s", url);
159
160
0
  apr_uri_t uri;
161
0
  apr_uri_parse(r->pool, url, &uri);
162
163
0
  char *domain = uri.hostname;
164
0
  if (uri.port_str != NULL)
165
0
    domain = apr_psprintf(r->pool, "%s:%s", domain, uri.port_str);
166
167
0
  return oidc_proto_webfinger_discovery(r, cfg, url, domain, issuer);
168
0
}