/src/mod_auth_openidc/src/metadata.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 | | * Copyright (C) 2013-2017 Ping Identity Corporation |
23 | | * All rights reserved. |
24 | | * |
25 | | * Thin orchestrator over the metadata subsystem. The per-domain helpers live |
26 | | * under src/metadata/ (provider.c, conf.c, client.c, oauth.c, jwks.c, util.c). |
27 | | * |
28 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
29 | | */ |
30 | | |
31 | | #include "metadata/internal.h" |
32 | | |
33 | | #include <apr_file_io.h> |
34 | | |
35 | | /* |
36 | | * get the metadata for a specified issuer |
37 | | * |
38 | | * fills the oidc_provider_t struct by reading and merging the provider, conf |
39 | | * and client metadata files for the issuer |
40 | | */ |
41 | | apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg_t *cfg, const char *issuer, oidc_provider_t **provider, |
42 | 0 | apr_byte_t allow_discovery) { |
43 | |
|
44 | 0 | apr_byte_t rc = FALSE; |
45 | | |
46 | | /* pointers to the parsed JSON metadata */ |
47 | 0 | oidc_json_t *j_provider = NULL; |
48 | 0 | oidc_json_t *j_client = NULL; |
49 | 0 | oidc_json_t *j_conf = NULL; |
50 | | |
51 | | /* allocate space for a parsed-and-merged metadata struct */ |
52 | 0 | *provider = oidc_cfg_provider_create(r->pool); |
53 | | |
54 | | /* |
55 | | * read and parse the provider, conf and client metadata respectively |
56 | | * NB: order is important here |
57 | | */ |
58 | |
|
59 | 0 | if (oidc_metadata_provider_get(r, cfg, issuer, &j_provider, allow_discovery) == FALSE) |
60 | 0 | goto end; |
61 | 0 | if (oidc_metadata_conf_get(r, issuer, &j_conf) == FALSE) |
62 | 0 | goto end; |
63 | | |
64 | | /* the provider metadata is parsed against the settings of this OP, so those of its conf |
65 | | * metadata that it depends on are applied to the (still empty) provider struct up front */ |
66 | 0 | oidc_metadata_conf_parse_pre_provider(r, cfg, j_conf, *provider); |
67 | |
|
68 | 0 | if (oidc_metadata_provider_parse(r, cfg, j_provider, *provider) == FALSE) |
69 | 0 | goto end; |
70 | 0 | if (oidc_metadata_conf_parse(r, cfg, j_conf, *provider) == FALSE) |
71 | 0 | goto end; |
72 | | |
73 | 0 | if (oidc_metadata_client_get(r, cfg, issuer, *provider, &j_client) == FALSE) |
74 | 0 | goto end; |
75 | 0 | if (oidc_metadata_client_parse(r, cfg, j_client, *provider) == FALSE) |
76 | 0 | goto end; |
77 | | |
78 | 0 | rc = TRUE; |
79 | |
|
80 | 0 | end: |
81 | |
|
82 | 0 | if (j_provider) |
83 | 0 | oidc_json_decref(j_provider); |
84 | 0 | if (j_conf) |
85 | 0 | oidc_json_decref(j_conf); |
86 | 0 | if (j_client) |
87 | 0 | oidc_json_decref(j_client); |
88 | |
|
89 | 0 | return rc; |
90 | 0 | } |
91 | | |
92 | | /* |
93 | | * get a list of configured OIDC providers based on the entries in the provider metadata directory |
94 | | */ |
95 | 0 | apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg_t *cfg, apr_array_header_t **list) { |
96 | 0 | apr_status_t rc; |
97 | 0 | apr_dir_t *dir; |
98 | 0 | apr_finfo_t fi; |
99 | 0 | char s_err[128]; |
100 | |
|
101 | 0 | oidc_debug(r, "enter"); |
102 | | |
103 | | /* A failed static-provider lookup may reach discovery without a metadata directory. */ |
104 | 0 | if (oidc_cfg_metadata_dir_get(cfg) == NULL) { |
105 | 0 | oidc_error(r, "" OIDCMetadataDir " is not set: no providers can be listed"); |
106 | 0 | return FALSE; |
107 | 0 | } |
108 | | |
109 | | /* open the metadata directory */ |
110 | 0 | if ((rc = apr_dir_open(&dir, oidc_cfg_metadata_dir_get(cfg), r->pool)) != APR_SUCCESS) { |
111 | 0 | oidc_error(r, "error opening metadata directory '%s' (%s)", oidc_cfg_metadata_dir_get(cfg), |
112 | 0 | apr_strerror(rc, s_err, sizeof(s_err))); |
113 | 0 | return FALSE; |
114 | 0 | } |
115 | | |
116 | | /* allocate some space in the array that will hold the list of providers */ |
117 | 0 | *list = apr_array_make(r->pool, 5, sizeof(const char *)); |
118 | | /* BTW: we could estimate the number in the array based on # directory entries... */ |
119 | | |
120 | | /* loop over the entries in the provider metadata directory */ |
121 | 0 | while (apr_dir_read(&fi, APR_FINFO_NAME, dir) == APR_SUCCESS) { |
122 | | |
123 | | /* skip "." and ".." entries */ |
124 | 0 | if (fi.name[0] == OIDC_CHAR_DOT) |
125 | 0 | continue; |
126 | | /* skip other non-provider entries */ |
127 | 0 | const char *ext = strrchr(fi.name, OIDC_CHAR_DOT); |
128 | 0 | if (ext == NULL) |
129 | 0 | continue; |
130 | 0 | ext++; |
131 | 0 | if (_oidc_strcmp(ext, OIDC_METADATA_SUFFIX_PROVIDER) != 0) |
132 | 0 | continue; |
133 | | |
134 | | /* get the issuer from the filename */ |
135 | 0 | const char *issuer = oidc_metadata_filename_to_issuer(r, fi.name); |
136 | | /* The legacy filename format strips the scheme, so use the issuer from |
137 | | * the provider document when it is available. This preserves HTTP |
138 | | * issuers while retaining the filename-derived fallback for malformed |
139 | | * entries, which will be rejected by the normal metadata validation. */ |
140 | 0 | const char *provider_path = apr_psprintf(r->pool, "%s/%s", oidc_cfg_metadata_dir_get(cfg), fi.name); |
141 | 0 | oidc_json_t *j_provider = NULL; |
142 | 0 | char *metadata_issuer = NULL; |
143 | 0 | if (oidc_metadata_file_read_json(r, provider_path, &j_provider) == TRUE) |
144 | 0 | oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &metadata_issuer, NULL); |
145 | | /* NB: oidc_json_object_get_string returns TRUE with a NULL value for an absent key, |
146 | | * so the fallback must check the value, not the return code */ |
147 | 0 | if (metadata_issuer != NULL) |
148 | 0 | issuer = metadata_issuer; |
149 | 0 | if (j_provider != NULL) |
150 | 0 | oidc_json_decref(j_provider); |
151 | | |
152 | | /* get the provider and client metadata, do all checks and registration if possible */ |
153 | 0 | oidc_provider_t *provider = NULL; |
154 | 0 | if (oidc_metadata_get(r, cfg, issuer, &provider, FALSE) == TRUE) { |
155 | | /* push the decoded issuer filename in to the array */ |
156 | 0 | APR_ARRAY_PUSH(*list, const char *) = oidc_cfg_provider_issuer_get(provider); |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | /* we're done, cleanup now */ |
161 | 0 | apr_dir_close(dir); |
162 | |
|
163 | 0 | return TRUE; |
164 | 0 | } |