/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/***************************************************************************
 * Copyright (C) 2017-2026 ZmartZone Holding BV
 * All rights reserved.
 *
 * DISCLAIMER OF WARRANTIES:
 *
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
 */

#include "cfg/provider.h"
#include "cfg/cfg_int.h"
#include "cfg/parse.h"
#include "proto/proto.h"

/*
 * Generate the declaration, initialization, and merge logic for simple provider members.
 * Members needing grouped or deep-copy handling remain explicit below.
 */
#define OIDC_PROVIDER_CFG_SIMPLE_MEMBERS(PTR, INT)                                                                     \
	PTR(char *, metadata_url)                                                                                      \
	PTR(char *, issuer)                                                                                            \
	PTR(char *, authorization_endpoint_url)                                                                        \
	PTR(char *, token_endpoint_url)                                                                                \
	PTR(char *, token_endpoint_auth)                                                                               \
	PTR(char *, token_endpoint_auth_alg)                                                                           \
	PTR(char *, token_endpoint_params)                                                                             \
	PTR(char *, userinfo_endpoint_url)                                                                             \
	PTR(char *, revocation_endpoint_url)                                                                           \
	PTR(char *, registration_endpoint_url)                                                                         \
	PTR(char *, pushed_authorization_request_endpoint_url)                                                         \
	PTR(char *, check_session_iframe)                                                                              \
	PTR(char *, end_session_endpoint)                                                                              \
	PTR(char *, client_id)                                                                                         \
	PTR(char *, client_secret)                                                                                     \
	PTR(char *, token_endpoint_tls_client_key)                                                                     \
	PTR(char *, token_endpoint_tls_client_key_pwd)                                                                 \
	PTR(char *, token_endpoint_tls_client_cert)                                                                    \
	PTR(char *, client_name)                                                                                       \
	PTR(char *, client_contact)                                                                                    \
	PTR(char *, registration_token)                                                                                \
	PTR(char *, registration_endpoint_json)                                                                        \
	PTR(char *, scope)                                                                                             \
	PTR(char *, response_type)                                                                                     \
	PTR(char *, response_mode)                                                                                     \
	PTR(char *, auth_request_params)                                                                               \
	PTR(char *, logout_request_params)                                                                             \
	PTR(char *, client_jwks_uri)                                                                                   \
	PTR(char *, id_token_signed_response_alg)                                                                      \
	PTR(char *, id_token_encrypted_response_alg)                                                                   \
	PTR(char *, id_token_encrypted_response_enc)                                                                   \
	PTR(char *, userinfo_signed_response_alg)                                                                      \
	PTR(char *, userinfo_encrypted_response_alg)                                                                   \
	PTR(char *, userinfo_encrypted_response_enc)                                                                   \
	PTR(char *, request_object)                                                                                    \
	PTR(oidc_proto_pkce_t *, pkce)                                                                                 \
	PTR(apr_array_header_t *, id_token_aud_values)                                                                 \
	INT(int, backchannel_logout_supported)                                                                         \
	INT(int, ssl_validate_server)                                                                                  \
	INT(int, validate_issuer)                                                                                      \
	INT(int, idtoken_iat_slack)                                                                                    \
	INT(int, session_max_duration)                                                                                 \
	INT(oidc_dpop_mode_t, dpop_mode)                                                                               \
	INT(oidc_cert_bound_tokens_t, cert_bound_tokens)                                                               \
	INT(int, dpop_supported)                                                                                       \
	INT(int, userinfo_refresh_interval)                                                                            \
	INT(oidc_userinfo_token_method_t, userinfo_token_method)                                                       \
	INT(oidc_auth_request_method_t, auth_request_method)                                                           \
	INT(oidc_profile_t, profile)                                                                                   \
	INT(int, response_require_iss)

#define OIDC_PROVIDER_M_DECL(type, name) type name;
#define OIDC_PROVIDER_M_INIT_PTR(type, name) provider->name = NULL;
#define OIDC_PROVIDER_M_INIT_INT(type, name) provider->name = OIDC_CONFIG_POS_INT_UNSET;
#define OIDC_PROVIDER_M_MERGE_PTR(type, name) dst->name = _oidc_cfg_merge_ptr(add->name, base->name);
#define OIDC_PROVIDER_M_MERGE_INT(type, name) dst->name = _oidc_cfg_merge_pos_int(add->name, base->name);

struct oidc_provider_t {
	OIDC_PROVIDER_CFG_SIMPLE_MEMBERS(OIDC_PROVIDER_M_DECL, OIDC_PROVIDER_M_DECL)
	/* grouped: uri/signed_uri/refresh_interval merge individually, jwk_list is deep-copied */
	oidc_jwks_uri_t jwks_uri;
	/* deep-copied on merge */
	apr_array_header_t *verify_public_keys;
	apr_array_header_t *client_keys;
};

/* Generate provider accessor bodies declared in provider.h. */
#define OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, type, def_val)                                                     \
                                                                                                                       \
	const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) {                     \
		oidc_cfg_t *cfg =                                                                                      \
		    (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);              \
		const char *rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, arg);                      \
		return OIDC_CONFIG_DIR_RV(cmd, rv);                                                                    \
	}                                                                                                              \
                                                                                                                       \
	type oidc_cfg_provider_##member##_get(const oidc_provider_t *provider) {                                       \
		return provider->member != NULL ? provider->member : def_val;                                          \
	}

// simple string
#define OIDC_PROVIDER_MEMBER_FUNCS_STR(member, def_val)                                                                \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		provider->member = apr_pstrdup(pool, arg);                                                             \
		return NULL;                                                                                           \
	};                                                                                                             \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, def_val)

#define OIDC_PROVIDER_MEMBER_FUNCS_FILE(member)                                                                        \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		return oidc_cfg_parse_filename(pool, arg, &provider->member);                                          \
	};                                                                                                             \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, NULL)

/* the cast keeps the sentinel comparison signed: `type` is an enum for several of these members, and
 * an enum with only non-negative enumerators has an unsigned underlying type, which would make the
 * bare comparison against the negative OIDC_CONFIG_POS_INT_UNSET a signed/unsigned one */
#define OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, type, def_val)                                                        \
	type oidc_cfg_provider_##member##_get(const oidc_provider_t *provider) {                                       \
		return (int)provider->member != OIDC_CONFIG_POS_INT_UNSET ? provider->member : def_val;                \
	}

// array of strings, int index
#define OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(member, fparse, type, def_val)                                              \
	void oidc_cfg_provider_##member##_int_set(oidc_provider_t *provider, type arg) {                               \
		provider->member = arg;                                                                                \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		const char *rv = NULL;                                                                                 \
		/* only read when fparse succeeds and has written it; seeded so the optimizer does not                 \
		 * have to prove that across the call (-Wmaybe-uninitialized) */                                       \
		type v = def_val;                                                                                      \
		rv = fparse(pool, arg, &v);                                                                            \
		if (rv == NULL)                                                                                        \
			provider->member = v;                                                                          \
		else                                                                                                   \
			provider->member = def_val;                                                                    \
		return rv;                                                                                             \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) {                     \
		oidc_cfg_t *cfg =                                                                                      \
		    (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);              \
		const char *rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, arg);                      \
		return OIDC_CONFIG_DIR_RV(cmd, rv);                                                                    \
	}                                                                                                              \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, type, def_val)

// string with validation routine (for metadata)
#define OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(member, fvalid, def_val)                                                  \
	const char *oidc_cfg_provider_##member##_valid(apr_pool_t *pool, const char *arg) {                            \
		return fvalid(pool, arg);                                                                              \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		const char *rv = oidc_cfg_provider_##member##_valid(pool, arg);                                        \
		if (rv == NULL)                                                                                        \
			provider->member = apr_pstrdup(pool, arg);                                                     \
		return rv;                                                                                             \
	}                                                                                                              \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, def_val)

#define OIDC_PROVIDER_MEMBER_FUNCS_INT(member, fparse, min_val, max_val, def_val)                                      \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_valid(apr_pool_t *pool, int arg) {                                    \
		return oidc_cfg_parse_is_valid_int(pool, arg, min_val, max_val);                                       \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) {           \
		const char *rv = oidc_cfg_provider_##member##_valid(pool, arg);                                        \
		if (rv == NULL)                                                                                        \
			provider->member = arg;                                                                        \
		else                                                                                                   \
			provider->member = def_val;                                                                    \
		return rv;                                                                                             \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *arg) {                     \
		oidc_cfg_t *cfg =                                                                                      \
		    (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);              \
		int v = -1;                                                                                            \
		const char *rv = fparse(cmd->pool, arg, &v);                                                           \
		if (rv == NULL)                                                                                        \
			rv = oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, v);                            \
		return OIDC_CONFIG_DIR_RV(cmd, rv);                                                                    \
	}                                                                                                              \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_GET_INT_DEF(member, int, def_val)

#define OIDC_PROVIDER_MEMBER_FUNCS_URL(member)                                                                         \
	OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(member, oidc_cfg_parse_is_valid_http_url, NULL)

/* URL accessor variant that preserves an empty string as an explicit endpoint disable. */
#define OIDC_PROVIDER_MEMBER_FUNCS_URL_EMPTY(member)                                                                   \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		const char *rv = oidc_cfg_parse_is_valid_http_url(pool, arg);                                          \
		if (rv == NULL)                                                                                        \
			provider->member = apr_pstrdup(pool, arg);                                                     \
		return rv;                                                                                             \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cmd_provider_##member##_set(cmd_parms *cmd, void *ptr, const char *args) {                    \
		oidc_cfg_t *cfg =                                                                                      \
		    (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);              \
		const char *w = ap_getword_conf(cmd->pool, &args);                                                     \
		if (*w == '\0' || *args != 0) {                                                                        \
			cfg->provider->member = "";                                                                    \
			return NULL;                                                                                   \
		}                                                                                                      \
		return oidc_cfg_provider_##member##_set(cmd->pool, cfg->provider, w);                                  \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_get(const oidc_provider_t *provider) {                                \
		return provider->member;                                                                               \
	}

#define OIDC_PROVIDER_MEMBER_FUNCS_FLAG(member, def_val)                                                               \
	OIDC_PROVIDER_MEMBER_FUNCS_INT(member, oidc_cfg_parse_boolean, 0, 1, def_val)

/*
 * passphrases
 */
#define OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(member)                                                             \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		return oidc_cfg_parse_passphrase(pool, arg, &provider->member);                                        \
	}                                                                                                              \
                                                                                                                       \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const char *, NULL)

OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(client_secret)
OIDC_PROVIDER_TYPE_MEMBER_FUNCS_PASSPHRASE(token_endpoint_tls_client_key_pwd)

/*
 * keys
 */
#define OIDC_PROVIDER_MEMBER_FUNCS_KEYS(member)                                                                        \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set_keys(apr_pool_t *pool, oidc_provider_t *provider,                 \
							  apr_array_header_t *arg) {                                   \
		provider->member = arg;                                                                                \
		return NULL;                                                                                           \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		return oidc_cfg_parse_public_key_files(pool, arg, &provider->member);                                  \
	}                                                                                                              \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const apr_array_header_t *, NULL)

OIDC_PROVIDER_MEMBER_FUNCS_KEYS(verify_public_keys)
OIDC_PROVIDER_MEMBER_FUNCS_KEYS(client_keys)

/*
 * string list
 */

#define OIDC_PROVIDER_MEMBER_FUNCS_STR_LIST(member)                                                                    \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set_str_list(apr_pool_t *pool, oidc_provider_t *provider,             \
							      apr_array_header_t *arg) {                               \
		provider->member = arg;                                                                                \
		return NULL;                                                                                           \
	}                                                                                                              \
                                                                                                                       \
	const char *oidc_cfg_provider_##member##_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {   \
		return oidc_cfg_string_list_add(pool, &provider->member, arg);                                         \
	}                                                                                                              \
	OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, const apr_array_header_t *, NULL)

/*
 * id token aud values
 */
OIDC_PROVIDER_MEMBER_FUNCS_STR_LIST(id_token_aud_values)

/*
 * PKCE
 */
#define OIDC_DEFAULT_PROVIDER_PKCE &oidc_pkce_s256

const char *oidc_cfg_provider_pkce_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {
	/* single source for both the valid values and the selected method */
	static const struct {
		const char *str;
		oidc_proto_pkce_t *method;
	} options[] = {{OIDC_PKCE_METHOD_PLAIN, &oidc_pkce_plain},
		       {OIDC_PKCE_METHOD_S256, &oidc_pkce_s256},
		       {OIDC_PKCE_METHOD_NONE, &oidc_pkce_none},
		       {NULL, NULL}};
	const char *strs[sizeof(options) / sizeof(options[0])];
	int i = 0;
	for (i = 0; options[i].str != NULL; i++) {
		if (_oidc_strcmp(arg, options[i].str) == 0) {
			provider->pkce = options[i].method;
			return NULL;
		}
		strs[i] = options[i].str;
	}
	strs[i] = NULL;
	return oidc_cfg_parse_is_valid_option(pool, arg, strs);
}

OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(pkce, const oidc_proto_pkce_t *, OIDC_DEFAULT_PROVIDER_PKCE)

/*
 * DPoP
 */
#define OIDC_DPOP_MODE_OFF_STR "off"
#define OIDC_DPOP_MODE_OPTIONAL_STR "optional"
#define OIDC_DPOP_MODE_REQUIRED_STR "required"

static const char *oidc_cfg_provider_parse_dop_method(apr_pool_t *pool, const char *arg, oidc_dpop_mode_t *mode) {
	static const oidc_cfg_option_t options[] = {
	    {OIDC_DPOP_MODE_OFF, OIDC_DPOP_MODE_OFF_STR},
	    {OIDC_DPOP_MODE_OPTIONAL, OIDC_DPOP_MODE_OPTIONAL_STR},
	    {OIDC_DPOP_MODE_REQUIRED, OIDC_DPOP_MODE_REQUIRED_STR},
	};
	return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)mode);
}

#define OIDC_DEFAULT_DPOP_MODE OIDC_DPOP_MODE_OFF

OIDC_PROVIDER_MEMBER_GET_INT_DEF(dpop_mode, oidc_dpop_mode_t, OIDC_DEFAULT_DPOP_MODE)

void oidc_cfg_provider_dpop_mode_int_set(oidc_provider_t *provider, oidc_dpop_mode_t arg) {
	provider->dpop_mode = arg;
}

const char *oidc_cfg_provider_dpop_mode_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {
	const char *rv = NULL;
	/* only read when the parse below succeeds and has written it; seeded so the optimizer does not
	 * have to prove that across the call (-Wmaybe-uninitialized) */
	oidc_dpop_mode_t v = OIDC_DEFAULT_DPOP_MODE;
	rv = oidc_cfg_provider_parse_dop_method(pool, arg, &v);
	if (rv == NULL)
		provider->dpop_mode = v;
	else
		provider->dpop_mode = OIDC_DEFAULT_DPOP_MODE;
	return rv;
}

const char *oidc_cmd_provider_dpop_mode_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	const char *rv = oidc_cfg_provider_dpop_mode_set(cmd->pool, cfg->provider, arg1);
	if ((rv == NULL) && arg2)
		rv = oidc_cfg_parse_boolean(cmd->pool, arg2, &cfg->dpop_api_enabled);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

/*
 * RFC 8705 certificate-bound access tokens
 */
#define OIDC_CERT_BOUND_TOKENS_OFF_STR "off"
#define OIDC_CERT_BOUND_TOKENS_AUTO_STR "auto"
#define OIDC_CERT_BOUND_TOKENS_ON_STR "on"

static const char *oidc_cfg_provider_parse_cert_bound_tokens(apr_pool_t *pool, const char *arg,
							     oidc_cert_bound_tokens_t *mode) {
	static const oidc_cfg_option_t options[] = {
	    {OIDC_CERT_BOUND_TOKENS_OFF, OIDC_CERT_BOUND_TOKENS_OFF_STR},
	    {OIDC_CERT_BOUND_TOKENS_AUTO, OIDC_CERT_BOUND_TOKENS_AUTO_STR},
	    {OIDC_CERT_BOUND_TOKENS_ON, OIDC_CERT_BOUND_TOKENS_ON_STR},
	};
	return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)mode);
}

#define OIDC_DEFAULT_CERT_BOUND_TOKENS OIDC_CERT_BOUND_TOKENS_AUTO

OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(cert_bound_tokens, oidc_cfg_provider_parse_cert_bound_tokens,
				   oidc_cert_bound_tokens_t, OIDC_DEFAULT_CERT_BOUND_TOKENS)

/*
 * whether the OP supports RFC 9449 DPoP, i.e. advertises "dpop_signing_alg_values_supported"
 *
 * NB: hand-written rather than generated because this is not configurable: it records what the
 *     provider metadata says, so there is no directive and no string setter for it
 */
void oidc_cfg_provider_dpop_supported_int_set(oidc_provider_t *provider, int arg) {
	provider->dpop_supported = arg;
}
OIDC_PROVIDER_MEMBER_GET_INT_DEF(dpop_supported, int, 0)

OIDC_PROVIDER_MEMBER_FUNCS_STR(issuer, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_URL(authorization_endpoint_url)
OIDC_PROVIDER_MEMBER_FUNCS_STR(auth_request_params, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_URL(token_endpoint_url)
OIDC_PROVIDER_MEMBER_FUNCS_STR(token_endpoint_params, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_URL(registration_endpoint_url)
OIDC_PROVIDER_MEMBER_FUNCS_URL(pushed_authorization_request_endpoint_url)
OIDC_PROVIDER_MEMBER_FUNCS_URL(check_session_iframe)
OIDC_PROVIDER_MEMBER_FUNCS_URL(end_session_endpoint)
OIDC_PROVIDER_MEMBER_FUNCS_STR(client_contact, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_STR(client_id, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_URL(client_jwks_uri)
OIDC_PROVIDER_MEMBER_FUNCS_STR(logout_request_params, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_URL(metadata_url)
OIDC_PROVIDER_MEMBER_FUNCS_STR(registration_endpoint_json, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_STR(request_object, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_FILE(token_endpoint_tls_client_cert)
OIDC_PROVIDER_MEMBER_FUNCS_FILE(token_endpoint_tls_client_key)

/* default scope requested from the OP */
#define OIDC_DEFAULT_SCOPE "openid"
OIDC_PROVIDER_MEMBER_FUNCS_STR(scope, OIDC_DEFAULT_SCOPE)

#define OIDC_DEFAULT_CLIENT_NAME "OpenID Connect Apache Module (mod_auth_openidc)"
OIDC_PROVIDER_MEMBER_FUNCS_STR(client_name, OIDC_DEFAULT_CLIENT_NAME)

/* whether the OP supports OpenID Connect Back-Channel Logout; when enabled, sessions are additionally
 * indexed by "sub" so a logout token that carries only "sub" can locate a session created with a "sid" */
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(backchannel_logout_supported, 0)

#define OIDC_DEFAULT_SSL_VALIDATE_SERVER 1
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(ssl_validate_server, OIDC_DEFAULT_SSL_VALIDATE_SERVER)

#define OIDC_DEFAULT_VALIDATE_ISSUER 1
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(validate_issuer, OIDC_DEFAULT_VALIDATE_ISSUER)

// define whether the iss parameter will be required in the response to the redirect uri by default to mitigate the IDP
// mixup attack only used from metadata in multi-provider setups
#define OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS 0
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(response_require_iss, OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS)
// only used from metadata in multi-provider setups
OIDC_PROVIDER_MEMBER_FUNCS_STR(registration_token, NULL)

OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(response_mode, oidc_cfg_parse_is_valid_response_mode, NULL)

#define OIDC_DEFAULT_RESPONSE_TYPE OIDC_PROTO_CODE
OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(response_type, oidc_cfg_parse_is_valid_response_type, OIDC_DEFAULT_RESPONSE_TYPE)

/*
 * whether response_type was explicitly configured, since the getter substitutes
 * OIDC_DEFAULT_RESPONSE_TYPE and thus cannot distinguish "set" from "default"
 */
apr_byte_t oidc_cfg_provider_response_type_is_set(const oidc_provider_t *provider) {
	return provider->response_type != NULL;
}

OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_signed_response_alg, oidc_cfg_parse_is_valid_signed_response_alg, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_encrypted_response_alg, oidc_cfg_parse_is_valid_encrypted_response_alg,
				     NULL)
OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(id_token_encrypted_response_enc, oidc_cfg_parse_is_valid_encrypted_response_enc,
				     NULL)

OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_signed_response_alg, oidc_cfg_parse_is_valid_signed_response_alg, NULL)
OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_encrypted_response_alg, oidc_cfg_parse_is_valid_encrypted_response_alg,
				     NULL)
OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_encrypted_response_enc, oidc_cfg_parse_is_valid_encrypted_response_enc,
				     NULL)

#define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET"
#define OIDC_AUTH_REQUEST_METHOD_POST_STR "POST"
#define OIDC_AUTH_REQUEST_METHOD_PAR_STR "PAR"

static const char *oidc_cfg_provider_parse_auth_request_method(apr_pool_t *pool, const char *arg,
							       oidc_auth_request_method_t *method) {
	static const oidc_cfg_option_t options[] = {
	    {OIDC_AUTH_REQUEST_METHOD_GET, OIDC_AUTH_REQUEST_METHOD_GET_STR},
	    {OIDC_AUTH_REQUEST_METHOD_POST, OIDC_AUTH_REQUEST_METHOD_POST_STR},
	    {OIDC_AUTH_REQUEST_METHOD_PAR, OIDC_AUTH_REQUEST_METHOD_PAR_STR},
	};
	return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)method);
}

#define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET
OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(auth_request_method, oidc_cfg_provider_parse_auth_request_method,
				   oidc_auth_request_method_t, OIDC_DEFAULT_AUTH_REQUEST_METHOD)

#define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header"
#define OIDC_USER_INFO_TOKEN_METHOD_POST_STR "post_param"

const char *oidc_cfg_provider_parse_userinfo_token_method(apr_pool_t *pool, const char *arg,
							  oidc_userinfo_token_method_t *method) {
	static const oidc_cfg_option_t options[] = {
	    {OIDC_USER_INFO_TOKEN_METHOD_HEADER, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR},
	    {OIDC_USER_INFO_TOKEN_METHOD_POST, OIDC_USER_INFO_TOKEN_METHOD_POST_STR}};
	return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)method);
}

#define OIDC_DEFAULT_USER_INFO_TOKEN_METHOD OIDC_USER_INFO_TOKEN_METHOD_HEADER
OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(userinfo_token_method, oidc_cfg_provider_parse_userinfo_token_method,
				   oidc_userinfo_token_method_t, OIDC_DEFAULT_USER_INFO_TOKEN_METHOD)

#define OIDC_IDTOKEN_IAT_SLACK_MIN 0
#define OIDC_IDTOKEN_IAT_SLACK_MAX 3600
#define OIDC_DEFAULT_IDTOKEN_IAT_SLACK 600

OIDC_PROVIDER_MEMBER_FUNCS_INT(idtoken_iat_slack, oidc_cfg_parse_int, OIDC_IDTOKEN_IAT_SLACK_MIN,
			       OIDC_IDTOKEN_IAT_SLACK_MAX, OIDC_DEFAULT_IDTOKEN_IAT_SLACK)

#define OIDC_SESSION_MAX_DURATION_MIN 15
#define OIDC_SESSION_MAX_DURATION_MAX (3600 * 24 * 365)
#define OIDC_DEFAULT_SESSION_MAX_DURATION (3600 * 8)

const char *oidc_cfg_provider_session_max_duration_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) {
	const char *rv = NULL;
	if (arg != 0)
		rv = oidc_cfg_parse_is_valid_int(pool, arg, OIDC_SESSION_MAX_DURATION_MIN,
						 OIDC_SESSION_MAX_DURATION_MAX);
	if (rv == NULL)
		provider->session_max_duration = arg;
	else
		provider->session_max_duration = OIDC_DEFAULT_SESSION_MAX_DURATION;
	return rv;
}

const char *oidc_cmd_provider_session_max_duration_set(cmd_parms *cmd, void *ptr, const char *arg) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	int v = -1;
	const char *rv = oidc_cfg_parse_int(cmd->pool, arg, &v);
	if (rv == NULL)
		rv = oidc_cfg_provider_session_max_duration_set(cmd->pool, cfg->provider, v);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

OIDC_PROVIDER_MEMBER_GET_INT_DEF(session_max_duration, int, OIDC_DEFAULT_SESSION_MAX_DURATION)

#define OIDC_JWKS_REFRESH_INTERVAL_MIN 300
#define OIDC_JWKS_REFRESH_INTERVAL_MAX (3600 * 24 * 365)
#define OIDC_DEFAULT_JWKS_REFRESH_INTERVAL 3600

const char *oidc_cfg_provider_jwks_uri_refresh_interval_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) {
	const char *rv =
	    oidc_cfg_parse_is_valid_int(pool, arg, OIDC_JWKS_REFRESH_INTERVAL_MIN, OIDC_JWKS_REFRESH_INTERVAL_MAX);
	if (rv == NULL)
		provider->jwks_uri.refresh_interval = arg;
	else
		provider->jwks_uri.refresh_interval = OIDC_DEFAULT_JWKS_REFRESH_INTERVAL;
	return rv;
}

const char *oidc_cmd_provider_jwks_uri_refresh_interval_set(cmd_parms *cmd, void *ptr, const char *arg) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	int v = 0;
	const char *rv = oidc_cfg_parse_int(cmd->pool, arg, &v);
	if (rv == NULL)
		rv = oidc_cfg_provider_jwks_uri_refresh_interval_set(cmd->pool, cfg->provider, v);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

int oidc_cfg_jwks_uri_refresh_interval_get(const oidc_jwks_uri_t *jwks_uri) {
	return jwks_uri->refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? jwks_uri->refresh_interval
								       : OIDC_DEFAULT_JWKS_REFRESH_INTERVAL;
}

int oidc_cfg_provider_jwks_uri_refresh_interval_get(const oidc_provider_t *provider) {
	return provider->jwks_uri.refresh_interval != OIDC_CONFIG_POS_INT_UNSET ? provider->jwks_uri.refresh_interval
										: OIDC_DEFAULT_JWKS_REFRESH_INTERVAL;
}

const oidc_jwks_uri_t *oidc_cfg_provider_jwks_uri_get(const oidc_provider_t *provider) {
	return &provider->jwks_uri;
}

const char *oidc_cfg_provider_jwks_uri_uri_get(const oidc_provider_t *provider) {
	return provider->jwks_uri.uri;
}

const char *oidc_cfg_provider_jwks_uri_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg) {
	const char *rv = oidc_cfg_parse_is_valid_http_url(pool, arg);
	if (rv == NULL)
		provider->jwks_uri.uri = apr_pstrdup(pool, arg);
	return rv;
}

const char *oidc_cmd_provider_jwks_uri_set(cmd_parms *cmd, void *ptr, const char *arg) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	const char *rv = oidc_cfg_provider_jwks_uri_set(cmd->pool, cfg->provider, arg);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

const char *oidc_cfg_provider_signed_jwks_uri_get(const oidc_provider_t *provider) {
	return provider->jwks_uri.signed_uri;
}

apr_array_header_t *oidc_cfg_provider_signed_jwks_uri_keys_get(const oidc_provider_t *provider) {
	return provider->jwks_uri.jwk_list;
}

const char *oidc_cfg_provider_signed_jwks_uri_keys_set(apr_pool_t *pool, oidc_provider_t *provider,
						       const oidc_json_t *json, apr_array_header_t *def_val) {
	const char *rv = NULL;
	oidc_jose_error_t err;

	if (json == NULL)
		goto end;

	if (oidc_is_jwk(json)) {
		oidc_jwk_t *jwk = NULL;
		if (oidc_jwk_parse_json(pool, json, &jwk, &err) != TRUE) {
			rv = apr_psprintf(pool, "oidc_jwk_parse_json failed for the specified JWK: %s",
					  oidc_jose_e2s(pool, err));
			goto end;
		}
		provider->jwks_uri.jwk_list = apr_array_make(pool, 1, sizeof(const oidc_jwk_t *));
		APR_ARRAY_PUSH(provider->jwks_uri.jwk_list, const oidc_jwk_t *) = jwk;
		goto end;
	}

	if (oidc_is_jwks(json)) {
		if (oidc_jwks_parse_json(pool, json, &provider->jwks_uri.jwk_list, &err) != TRUE)
			rv = apr_psprintf(pool, "oidc_jwks_parse_json failed for the specified JWKs: %s",
					  oidc_jose_e2s(pool, err));
		goto end;
	}

	rv = apr_psprintf(pool, "invalid JWK/JWKs argument");

end:

	/* Copy default verification keys so every provider owns and can release its JWK list. */
	if ((json == NULL) || (rv != NULL))
		provider->jwks_uri.jwk_list = oidc_jwk_list_copy(pool, def_val);

	return rv;
}

const char *oidc_cfg_provider_signed_jwks_uri_set(apr_pool_t *pool, oidc_provider_t *provider, const char *arg1,
						  const char *arg2) {
	const char *rv = NULL;
	char *s_err = NULL;
	oidc_json_t *json = NULL;

	if ((arg1 != NULL) && (_oidc_strcmp(arg1, "") != 0)) {
		rv = oidc_cfg_parse_is_valid_http_url(pool, arg1);
		if (rv != NULL)
			goto end;
		provider->jwks_uri.signed_uri = apr_pstrdup(pool, arg1);
	}

	if ((arg2 == NULL) || (_oidc_strcmp(arg2, "") == 0))
		goto end;

	if (oidc_json_parse(pool, arg2, 0, &json, &s_err) == FALSE) {
		rv = apr_psprintf(pool, "parsing the 2nd argument as JSON failed: %s", s_err);
		goto end;
	}

	rv = oidc_cfg_provider_signed_jwks_uri_keys_set(pool, provider, json, NULL);

end:

	if (json)
		oidc_json_decref(json);

	return rv;
}

const char *oidc_cmd_provider_signed_jwks_uri_set(cmd_parms *cmd, void *ptr, const char *arg1, const char *arg2) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	const char *rv = oidc_cfg_provider_signed_jwks_uri_set(cmd->pool, cfg->provider, arg1, arg2);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

const char *oidc_cfg_provider_token_endpoint_auth_set(apr_pool_t *pool, const oidc_cfg_t *cfg,
						      oidc_provider_t *provider, const char *arg) {
	return oidc_cfg_endpoint_auth_set(pool, cfg, arg, &provider->token_endpoint_auth,
					  &provider->token_endpoint_auth_alg);
}

const char *oidc_cmd_provider_token_endpoint_auth_set(cmd_parms *cmd, void *ptr, const char *arg) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	const char *rv = oidc_cfg_provider_token_endpoint_auth_set(cmd->pool, cfg, cfg->provider, arg);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

const char *oidc_cfg_provider_token_endpoint_auth_get(const oidc_provider_t *provider) {
	return provider->token_endpoint_auth;
}

const char *oidc_cfg_provider_token_endpoint_auth_alg_get(const oidc_provider_t *provider) {
	return provider->token_endpoint_auth_alg;
}

#define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0
#define OIDC_USERINFO_REFRESH_INTERVAL_MAX (3600 * 24 * 365)
#define OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL -1

const char *oidc_cfg_provider_userinfo_refresh_interval_set(apr_pool_t *pool, oidc_provider_t *provider, int arg) {
	const char *rv = oidc_cfg_parse_is_valid_int(pool, arg, OIDC_USERINFO_REFRESH_INTERVAL_MIN,
						     OIDC_USERINFO_REFRESH_INTERVAL_MAX);
	if (rv == NULL)
		provider->userinfo_refresh_interval = arg;
	else
		provider->userinfo_refresh_interval = OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL;
	return rv;
}

const char *oidc_cmd_provider_userinfo_refresh_interval_set(cmd_parms *cmd, void *ptr, const char *arg1,
							    const char *arg2) {
	oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(cmd->server->module_config, &auth_openidc_module);
	int v = 0;
	const char *rv = oidc_cfg_parse_int(cmd->pool, arg1, &v);
	if (rv == NULL)
		rv = oidc_cfg_provider_userinfo_refresh_interval_set(cmd->pool, cfg->provider, v);
	if ((rv == NULL) && arg2)
		rv = oidc_cfg_parse_action_on_error_refresh_as(
		    cmd->pool, arg2, (oidc_on_error_action_t *)&cfg->action_on_userinfo_error);
	return OIDC_CONFIG_DIR_RV(cmd, rv);
}

int oidc_cfg_provider_userinfo_refresh_interval_get(const oidc_provider_t *provider) {
	return provider->userinfo_refresh_interval != OIDC_CONFIG_POS_INT_UNSET
		   ? provider->userinfo_refresh_interval
		   : OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL;
}

/* Endpoint URLs where an empty string explicitly disables the endpoint. */
OIDC_PROVIDER_MEMBER_FUNCS_URL_EMPTY(userinfo_endpoint_url)
OIDC_PROVIDER_MEMBER_FUNCS_URL_EMPTY(revocation_endpoint_url)

#define OIDC_PROFILE_OIDC10_STR "OIDC10"
#define OIDC_PROFILE_FAPI20_STR "FAPI20"

const char *oidc_cfg_provider_parse_profile(apr_pool_t *pool, const char *arg, oidc_profile_t *profile) {
	static const oidc_cfg_option_t options[] = {{OIDC_PROFILE_OIDC10, OIDC_PROFILE_OIDC10_STR},
						    {OIDC_PROFILE_FAPI20, OIDC_PROFILE_FAPI20_STR}};
	return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)profile);
}
#define OIDC_DEFAULT_PROFILE OIDC_PROFILE_OIDC10
OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(profile, oidc_cfg_provider_parse_profile, oidc_profile_t, OIDC_DEFAULT_PROFILE)

/*
 * base
 */

static void oidc_cfg_provider_init(oidc_provider_t *provider) {
	OIDC_PROVIDER_CFG_SIMPLE_MEMBERS(OIDC_PROVIDER_M_INIT_PTR, OIDC_PROVIDER_M_INIT_INT)
	provider->jwks_uri.uri = NULL;
	provider->jwks_uri.refresh_interval = OIDC_CONFIG_POS_INT_UNSET;
	provider->jwks_uri.signed_uri = NULL;
	provider->jwks_uri.jwk_list = NULL;
	provider->verify_public_keys = NULL;
	provider->client_keys = NULL;
}

void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_provider_t *base,
			     const oidc_provider_t *add) {
	OIDC_PROVIDER_CFG_SIMPLE_MEMBERS(OIDC_PROVIDER_M_MERGE_PTR, OIDC_PROVIDER_M_MERGE_INT)
	/* Merge token endpoint method and optional algorithm as one setting. */
	if (add->token_endpoint_auth != NULL)
		dst->token_endpoint_auth_alg = add->token_endpoint_auth_alg;
	dst->jwks_uri.uri = _oidc_cfg_merge_ptr(add->jwks_uri.uri, base->jwks_uri.uri);
	dst->jwks_uri.refresh_interval =
	    _oidc_cfg_merge_pos_int(add->jwks_uri.refresh_interval, base->jwks_uri.refresh_interval);
	dst->jwks_uri.signed_uri = _oidc_cfg_merge_ptr(add->jwks_uri.signed_uri, base->jwks_uri.signed_uri);
	dst->jwks_uri.jwk_list =
	    oidc_jwk_list_copy(pool, _oidc_cfg_merge_ptr(add->jwks_uri.jwk_list, base->jwks_uri.jwk_list));
	dst->verify_public_keys =
	    oidc_jwk_list_copy(pool, _oidc_cfg_merge_ptr(add->verify_public_keys, base->verify_public_keys));
	dst->client_keys = oidc_jwk_list_copy(pool, _oidc_cfg_merge_ptr(add->client_keys, base->client_keys));
}

oidc_provider_t *oidc_cfg_provider_create(apr_pool_t *pool) {
	oidc_provider_t *provider = apr_pcalloc(pool, sizeof(oidc_provider_t));
	oidc_cfg_provider_init(provider);
	return provider;
}

oidc_provider_t *oidc_cfg_provider_copy(apr_pool_t *pool, const oidc_provider_t *src) {
	oidc_provider_t *dst = oidc_cfg_provider_create(pool);
	oidc_cfg_provider_merge(pool, dst, dst, src);
	return dst;
}

void oidc_cfg_provider_destroy(oidc_provider_t *provider) {
	if (provider == NULL)
		return;
	oidc_jwk_list_destroy(provider->jwks_uri.jwk_list);
	provider->jwks_uri.jwk_list = NULL;
	oidc_jwk_list_destroy(provider->verify_public_keys);
	provider->verify_public_keys = NULL;
	oidc_jwk_list_destroy(provider->client_keys);
	provider->client_keys = NULL;
}
