#include "common/OidcRpConfig.h" #include #include #include #include #include namespace visione::oidc { namespace { std::vector readStringArray(const Json::Value &value) { std::vector out; if (!value.isArray()) { return out; } out.reserve(value.size()); for (const auto &item : value) { if (item.isString()) { out.push_back(item.asString()); } } return out; } Json::Value readJsonFile(const std::string &path) { std::ifstream input(path); if (!input.is_open()) { throw std::runtime_error("oidc_jwks_file_not_found"); } Json::Value value; input >> value; return value; } bool fileExists(const std::string &path) { return !path.empty() && std::filesystem::exists(path); } } // namespace RpConfig loadRpConfig() { const auto &root = drogon::app().getCustomConfig(); const auto &oidcRp = root["oidc_rp"]; const auto &federation = root["oidc_federation"]; RpConfig config; config.enabled = oidcRp.get("enabled", false).asBool(); config.baseUrl = oidcRp.get("base_url", "").asString(); config.clientId = oidcRp.get("client_id", "").asString(); config.redirectUri = oidcRp.get("redirect_uri", "").asString(); config.postLoginRedirectUri = oidcRp.get("post_login_redirect_uri", "").asString(); config.organizationName = oidcRp.get("organization_name", "").asString(); config.clientName = oidcRp.get("client_name", "").asString(); config.contacts = readStringArray(oidcRp["contacts"]); config.jwksPath = oidcRp.get("jwks_path", "").asString(); config.federationJwksPath = oidcRp.get("federation_jwks_path", "").asString(); config.trustAnchors = readStringArray(federation["trust_anchors"]); const auto &providers = federation["identity_providers"]; if (providers.isObject()) { for (const auto &name : providers.getMemberNames()) { const auto &item = providers[name]; RpProviderConfig provider; provider.profile = item.get("profile", name).asString(); provider.issuer = item.get("issuer", "").asString(); provider.trustAnchor = item.get("trust_anchor", "").asString(); config.providers.push_back(std::move(provider)); } } const auto jwksRoot = readJsonFile(config.jwksPath); Json::Value federationRoot; if (fileExists(config.federationJwksPath)) { federationRoot = readJsonFile(config.federationJwksPath); } else { federationRoot["keys"] = jwksRoot["federation"]; } const auto &federationKeys = federationRoot.isObject() ? federationRoot["keys"] : federationRoot; if (federationKeys.isArray()) { for (const auto &item : federationKeys) { config.federationJwks.push_back(parseJwk(item)); } } if (jwksRoot["core"].isArray()) { for (const auto &item : jwksRoot["core"]) { config.coreJwks.push_back(parseJwk(item)); } } config.trustMark = jwksRoot["trust_mark"]; return config; } Json::Value rpMetadataDocument(const RpConfig &config) { Json::Value metadata; metadata["openid_relying_party"]["organization_name"] = config.organizationName; metadata["openid_relying_party"]["application_type"] = "web"; metadata["openid_relying_party"]["client_id"] = config.clientId; metadata["openid_relying_party"]["client_registration_types"].append( "automatic"); metadata["openid_relying_party"]["client_name"] = config.clientName; metadata["openid_relying_party"]["subject_type"] = "pairwise"; metadata["openid_relying_party"]["redirect_uris"].append(config.redirectUri); metadata["openid_relying_party"]["response_types"].append("code"); metadata["openid_relying_party"]["grant_types"].append("refresh_token"); metadata["openid_relying_party"]["grant_types"].append("authorization_code"); metadata["openid_relying_party"]["token_endpoint_auth_method"] = "private_key_jwt"; metadata["openid_relying_party"]["id_token_signed_response_alg"] = "RS256"; metadata["openid_relying_party"]["userinfo_signed_response_alg"] = "RS256"; metadata["openid_relying_party"]["userinfo_encrypted_response_alg"] = "RSA-OAEP"; metadata["openid_relying_party"]["userinfo_encrypted_response_enc"] = "A256CBC-HS512"; Json::Value publicJwks; publicJwks["keys"] = Json::arrayValue; for (const auto &jwk : config.coreJwks) { publicJwks["keys"].append(jwkToPublicJson(jwk)); } metadata["openid_relying_party"]["jwks"] = publicJwks; metadata["openid_relying_party"]["jwks_uri"] = config.clientId + "/openid_relying_party/jwks.json"; metadata["openid_relying_party"]["signed_jwks_uri"] = config.clientId + "/openid_relying_party/jwks.jose"; for (const auto &contact : config.contacts) { metadata["openid_relying_party"]["contacts"].append(contact); } return metadata; } Json::Value rpPublicJwksDocument(const RpConfig &config) { Json::Value body; body["keys"] = Json::arrayValue; for (const auto &jwk : config.coreJwks) { body["keys"].append(jwkToPublicJson(jwk)); } return body; } Json::Value entityConfigurationDocument(const RpConfig &config) { Json::Value body; body["exp"] = static_cast(std::time(nullptr) + 2880 * 60); body["iat"] = static_cast(std::time(nullptr)); body["iss"] = config.clientId; body["sub"] = config.clientId; body["jwks"]["keys"] = Json::arrayValue; for (const auto &jwk : config.federationJwks) { body["jwks"]["keys"].append(jwkToPublicJson(jwk)); } body["metadata"] = rpMetadataDocument(config); body["metadata"]["federation_entity"]["federation_resolve_endpoint"] = config.clientId + "/resolve"; body["metadata"]["federation_entity"]["organization_name"] = config.organizationName; body["metadata"]["federation_entity"]["homepage_uri"] = config.baseUrl; body["metadata"]["federation_entity"]["policy_uri"] = config.baseUrl; body["metadata"]["federation_entity"]["logo_uri"] = config.baseUrl; for (const auto &contact : config.contacts) { body["metadata"]["federation_entity"]["contacts"].append(contact); } for (const auto &anchor : config.trustAnchors) { body["authority_hints"].append(anchor); } if (!config.trustMark.isNull()) { body["trust_marks"].append(config.trustMark); } return body; } std::optional findProviderConfig(const RpConfig &config, const std::string &provider) { for (const auto &candidate : config.providers) { if (candidate.profile == provider || candidate.issuer == provider) { return candidate; } } return std::nullopt; } } // namespace visione::oidc