-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathconfig_manager.hpp
More file actions
658 lines (563 loc) · 22.8 KB
/
config_manager.hpp
File metadata and controls
658 lines (563 loc) · 22.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
#pragma once
#include <crow.h>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <yaml-cpp/yaml.h>
#include "route_translator.hpp"
#include "extended_yaml_parser.hpp"
#include "path_utils.hpp"
namespace flapi {
struct TimeInterval {
static std::optional<std::chrono::seconds> parseInterval(const std::string& interval);
};
struct ConnectionConfig {
std::string init;
std::unordered_map<std::string, std::string> properties;
bool log_queries = false;
bool log_parameters = false;
std::string allow;
const std::string& getInit() const { return init; }
void setInit(const std::string& initSql) { init = initSql; }
};
struct HeartbeatConfig {
bool enabled = false;
std::unordered_map<std::string, std::string> params;
};
struct RateLimitConfig {
bool enabled = false;
int max = 100;
int interval = 60;
// W1.4: how requests are grouped into buckets. "ip" preserves the
// historic default; "user" / "user-or-ip" enable per-principal limits.
std::string key_strategy = "ip";
};
struct AuthUser {
std::string username;
std::string password;
std::vector<std::string> roles;
};
struct AuthFromSecretManagerConfig {
std::string secret_name;
std::string region;
std::string secret_id;
std::string secret_key;
std::string secret_table;
std::string init;
const std::string& getInit() const { return init; }
void setInit(const std::string& initSql) { init = initSql; }
};
struct OIDCConfig {
// Basic OIDC settings
std::string issuer_url; // e.g., https://accounts.google.com
std::string client_id; // OAuth 2.0 client ID
std::string client_secret; // Client secret (from env if needed)
// Provider type for presets
std::string provider_type = "generic"; // "google", "microsoft", "keycloak", etc.
// Token validation
std::vector<std::string> allowed_audiences; // Expected 'aud' claims
bool verify_expiration = true;
int clock_skew_seconds = 300; // 5 minute tolerance
// Claim mapping
std::string username_claim = "sub"; // Which claim contains username
std::string email_claim = "email";
std::string roles_claim = "roles";
std::string groups_claim = "groups";
// Nested claim paths (for realm_access.roles style)
std::string role_claim_path; // "realm_access.roles" for Keycloak
// OAuth flows
bool enable_client_credentials = false;
bool enable_refresh_tokens = false;
std::vector<std::string> scopes;
// JWKS caching
int jwks_cache_hours = 24; // How long to cache JWKS
};
struct AuthConfig {
bool enabled = false;
std::string type;
std::vector<AuthUser> users;
std::optional<AuthFromSecretManagerConfig> from_aws_secretmanager;
std::string jwt_secret;
std::string jwt_issuer;
std::optional<OIDCConfig> oidc; // NEW: OIDC configuration
};
struct ValidatorConfig {
std::string type;
std::string regex;
int min = 0;
int max = 0;
std::string minDate;
std::string maxDate;
std::string minTime;
std::string maxTime;
std::vector<std::string> allowedValues;
bool preventSqlInjection = true; // New field for SQL injection prevention
};
struct RequestFieldConfig {
std::string fieldName;
std::string fieldIn;
std::string description;
bool required = false;
std::string defaultValue;
std::vector<ValidatorConfig> validators;
};
struct CacheConfig {
bool enabled = false;
std::string table;
std::string schema = "cache";
std::optional<std::string> schedule;
std::vector<std::string> primary_keys;
struct CursorConfig {
std::string column;
std::string type;
};
std::optional<CursorConfig> cursor;
std::optional<std::string> rollback_window;
struct RetentionConfig {
std::optional<std::size_t> keep_last_snapshots;
std::optional<std::string> max_snapshot_age;
} retention;
std::optional<std::string> delete_handling;
std::optional<std::string> template_file;
bool invalidate_on_write = false; // Invalidate cache after write operations
bool refresh_on_write = false; // Refresh cache immediately after write operations
bool hasCursor() const { return cursor.has_value(); }
bool hasPrimaryKey() const { return !primary_keys.empty(); }
bool hasTemplate() const { return template_file.has_value(); }
std::chrono::seconds getRefreshTimeInSeconds() const;
};
struct OperationConfig {
enum Type { Read, Write };
Type type = Type::Read; // Default to read for backward compatibility
bool returns_data = false; // For RETURNING clauses in INSERT/UPDATE
bool transaction = true; // Wrap writes in transactions (default: safe)
bool validate_before_write = true; // Stricter validation for writes (default: safe)
};
struct EndpointConfig {
std::string urlPath;
std::string method;
bool request_fields_validation = false;
std::vector<RequestFieldConfig> request_fields;
std::string templateSource;
std::vector<std::string> connection;
bool with_pagination = true;
RateLimitConfig rate_limit;
AuthConfig auth;
CacheConfig cache;
HeartbeatConfig heartbeat;
OperationConfig operation; // Operation configuration (read/write, transaction, etc.)
// Path to the YAML configuration file for this endpoint (for reloading)
std::string config_file_path;
// MCP-specific metadata (optional)
struct MCPToolInfo {
std::string name;
std::string description;
std::string result_mime_type = "application/json";
};
struct MCPResourceInfo {
std::string name;
std::string description;
std::string mime_type = "application/json";
};
struct MCPPromptInfo {
std::string name;
std::string description;
std::string template_content;
std::vector<std::string> arguments;
};
std::optional<MCPToolInfo> mcp_tool;
std::optional<MCPResourceInfo> mcp_resource;
std::optional<MCPPromptInfo> mcp_prompt;
// Endpoint type enumeration
enum class Type {
REST,
MCP_Tool,
MCP_Resource,
MCP_Prompt,
Unknown
};
// Helper methods to check if this is an MCP entity
bool isMCPEntity() const { return mcp_tool.has_value() || mcp_resource.has_value() || mcp_prompt.has_value(); }
bool isRESTEndpoint() const { return !urlPath.empty(); }
bool isMCPTool() const { return mcp_tool.has_value(); }
bool isMCPResource() const { return mcp_resource.has_value(); }
bool isMCPPrompt() const { return mcp_prompt.has_value(); }
// Get the type of this endpoint
Type getType() const {
if (!urlPath.empty()) return Type::REST;
if (mcp_tool.has_value()) return Type::MCP_Tool;
if (mcp_resource.has_value()) return Type::MCP_Resource;
if (mcp_prompt.has_value()) return Type::MCP_Prompt;
return Type::Unknown;
}
// Get the unique name/path of this endpoint (abstracted across REST and MCP)
std::string getName() const {
if (!urlPath.empty()) {
return urlPath;
} else if (mcp_tool.has_value()) {
return mcp_tool->name;
} else if (mcp_resource.has_value()) {
return mcp_resource->name;
} else if (mcp_prompt.has_value()) {
return mcp_prompt->name;
}
return "";
}
// Get the URL slug for this endpoint (centralized slugging logic)
// REST endpoints: convert path to slug (e.g., /customers/ → customers-slash)
// MCP entities: use name as-is (e.g., customer_lookup → customer_lookup)
std::string getSlug() const {
if (!urlPath.empty()) {
return PathUtils::pathToSlug(urlPath);
} else {
// MCP names are already URL-safe, use as-is
return getName();
}
}
// Get a human-readable identifier for this endpoint (for logging/debugging)
std::string getIdentifier() const {
if (!urlPath.empty()) {
return "REST endpoint: " + urlPath;
} else if (mcp_tool.has_value()) {
return "MCP tool: " + mcp_tool->name;
} else if (mcp_resource.has_value()) {
return "MCP resource: " + mcp_resource->name;
} else if (mcp_prompt.has_value()) {
return "MCP prompt: " + mcp_prompt->name;
} else {
return "unknown endpoint";
}
}
// Get a short description for logging (more compact than getIdentifier)
std::string getShortDescription() const {
switch (getType()) {
case Type::REST:
return method + " " + urlPath;
case Type::MCP_Tool:
return "MCP Tool: " + mcp_tool->name;
case Type::MCP_Resource:
return "MCP Resource: " + mcp_resource->name;
case Type::MCP_Prompt:
return "MCP Prompt: " + mcp_prompt->name;
default:
return "unknown";
}
}
// Check if a given path/name matches this endpoint
bool matchesPath(const std::string& path) const;
// Check if this endpoint refers to the same logical endpoint as another
bool isSameEndpoint(const EndpointConfig& other) const {
if (getType() != other.getType()) return false;
switch (getType()) {
case Type::REST:
return urlPath == other.urlPath;
case Type::MCP_Tool:
return mcp_tool->name == other.mcp_tool->name;
case Type::MCP_Resource:
return mcp_resource->name == other.mcp_resource->name;
case Type::MCP_Prompt:
return mcp_prompt->name == other.mcp_prompt->name;
default:
return false;
}
}
// Validate this endpoint configuration and return errors
std::vector<std::string> validateSelf() const {
std::vector<std::string> errors;
switch (getType()) {
case Type::REST:
if (urlPath.empty()) {
errors.push_back("url-path cannot be empty");
}
if (!urlPath.empty() && urlPath[0] != '/') {
errors.push_back("url-path must start with /");
}
break;
case Type::MCP_Tool:
if (mcp_tool->name.empty()) {
errors.push_back("mcp-tool.name cannot be empty");
}
break;
case Type::MCP_Resource:
if (mcp_resource->name.empty()) {
errors.push_back("mcp-resource.name cannot be empty");
}
break;
case Type::MCP_Prompt:
if (mcp_prompt->name.empty()) {
errors.push_back("mcp-prompt.name cannot be empty");
}
break;
case Type::Unknown:
errors.push_back("Endpoint must define url-path, mcp-tool, mcp-resource, or mcp-prompt");
break;
}
return errors;
}
};
enum class EndpointJsonStyle {
HyphenCase,
CamelCase
};
struct MCPServerConfig {
bool enabled = false;
std::string server_name = "flapi-mcp-server";
std::string server_version = "0.1.0";
std::string protocol_version = "2024-11-05";
std::vector<std::string> capabilities = {"tools", "resources", "prompts", "sampling"};
bool stdio_transport = false; // Use HTTP transport by default
int mcp_port = 8081; // Different port from REST API
std::string mcp_base_path = "/mcp";
};
struct MCPMethodAuthConfig {
bool required = true;
};
struct MCPAuthConfig {
bool enabled = false;
std::string type = "bearer"; // "basic", "bearer", or "oidc"
std::vector<AuthUser> users; // For Basic auth - inline user list
std::string jwt_secret;
std::string jwt_issuer = "flapi";
std::optional<OIDCConfig> oidc; // NEW: OIDC configuration
std::unordered_map<std::string, MCPMethodAuthConfig> methods;
};
struct MCPConfig {
bool enabled = true;
int port = 8081;
MCPAuthConfig auth;
std::string instructions; // Inline instructions content
std::string instructions_file; // Path to markdown file (optional)
};
struct DuckDBConfig {
std::unordered_map<std::string, std::string> settings;
std::string db_path; // New field for database path
std::vector<std::string> default_extensions;
};
struct TemplateConfig {
std::string path;
std::vector<std::string> environment_whitelist;
bool isEnvironmentVariableAllowed(const std::string& varName) const {
if (environment_whitelist.empty()) {
return false; // If no whitelist is specified, allow all variables
}
for (const auto& pattern : environment_whitelist) {
std::regex regex(pattern);
if (std::regex_match(varName, regex)) {
return true;
}
}
return false;
}
};
struct HttpsConfig {
bool enabled = false;
std::string ssl_cert_file;
std::string ssl_key_file;
};
struct GlobalHeartbeatConfig {
bool enabled = false;
std::chrono::seconds workerInterval = std::chrono::seconds(60);
};
struct DuckLakeRetentionConfig {
std::optional<std::size_t> keep_last_snapshots;
std::optional<std::string> max_snapshot_age;
};
struct DuckLakeCompactionConfig {
bool enabled = false;
std::optional<std::string> schedule;
};
struct DuckLakeSchedulerConfig {
bool enabled = false;
std::optional<std::string> scan_interval;
};
struct DuckLakeConfig {
bool enabled = false;
std::string alias = "cache";
std::string metadata_path;
std::string data_path;
bool override_data_path = false;
DuckLakeRetentionConfig retention;
DuckLakeCompactionConfig compaction;
DuckLakeSchedulerConfig scheduler;
std::optional<std::size_t> data_inlining_row_limit;
};
struct StorageCacheConfig {
bool enabled = true;
std::chrono::seconds ttl{300}; // 5 minutes default
size_t max_size_bytes = 50UL * 1024UL * 1024UL; // 50 MB default
};
struct StorageConfig {
StorageCacheConfig cache;
};
// Forward declarations
class EndpointConfigParser;
class ConfigLoader;
class EndpointRepository;
class ConfigValidator;
class ConfigSerializer;
class IFileProvider;
class ConfigManager {
// Allow EndpointConfigParser to access protected parsing methods
friend class EndpointConfigParser;
public:
explicit ConfigManager(const std::filesystem::path& config_file);
// Virtual destructor (defined in implementation file to handle unique_ptr cleanup)
virtual ~ConfigManager();
// Delete copy constructor and copy assignment (unique_ptr members are non-copyable)
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
// Move semantics (defined in implementation file with complete types)
ConfigManager(ConfigManager&&) noexcept;
ConfigManager& operator=(ConfigManager&&) noexcept;
void loadConfig();
void loadEndpointConfigsRecursively(const std::filesystem::path& dir);
void loadEndpointConfig(const std::string& config_file);
// Getters for configuration values
std::string getProjectName() const;
std::string getProjectDescription() const;
std::string getServerName() const;
int getHttpPort() const;
void setHttpPort(int port);
virtual std::string getTemplatePath() const;
std::string getCacheSchema() const;
const std::unordered_map<std::string, ConnectionConfig>& getConnections() const;
const RateLimitConfig& getRateLimitConfig() const;
const DuckDBConfig& getDuckDBConfig() const;
const HttpsConfig& getHttpsConfig() const;
bool isHttpsEnforced() const;
bool isAuthEnabled() const;
std::optional<OIDCConfig> getGlobalOIDCConfig() const;
const EndpointConfig* getEndpointForPath(const std::string& path) const;
const EndpointConfig* getEndpointForPathAndMethod(const std::string& path, const std::string& httpMethod) const;
const std::vector<EndpointConfig>& getEndpoints() const;
const TemplateConfig& getTemplateConfig() const;
std::string getBasePath() const;
std::string getDuckDBPath() const;
ExtendedYamlParser& getYamlParser() { return yaml_parser; }
std::filesystem::path getFullTemplatePath() const;
// Get the file provider for VFS operations (local or remote file access)
std::shared_ptr<IFileProvider> getFileProvider() const;
const GlobalHeartbeatConfig& getGlobalHeartbeatConfig() const { return global_heartbeat_config; }
const DuckLakeConfig& getDuckLakeConfig() const { return ducklake_config; }
const MCPConfig& getMCPConfig() const { return mcp_config; }
const StorageConfig& getStorageConfig() const { return storage_config; }
bool isTelemetryEnabled() const { return telemetry_enabled; }
// Load MCP server instructions (inline or from file)
std::string loadMCPInstructions() const;
void refreshConfig();
void addEndpoint(const EndpointConfig& endpoint);
bool removeEndpointByPath(const std::string& path);
bool replaceEndpoint(const EndpointConfig& endpoint);
std::unordered_map<std::string, std::string> getPropertiesForTemplates(const std::string& connectionName) const;
crow::json::wvalue getFlapiConfig() const;
crow::json::wvalue getEndpointsConfig() const;
crow::json::wvalue serializeEndpointConfig(const EndpointConfig& config, EndpointJsonStyle style) const;
EndpointConfig deserializeEndpointConfig(const crow::json::rvalue& json) const;
// YAML serialization/deserialization (for export, debugging)
std::string serializeEndpointConfigToYaml(const EndpointConfig& config) const;
EndpointConfig deserializeEndpointConfigFromYaml(const std::string& yaml_content) const;
// Validation (does not modify files - preserves comments, formatting)
struct ValidationResult {
bool valid;
std::vector<std::string> errors;
std::vector<std::string> warnings;
};
ValidationResult validateEndpointConfig(const EndpointConfig& config) const;
ValidationResult validateEndpointConfigFromYaml(const std::string& yaml_content) const;
ValidationResult validateEndpointConfigFile(const std::filesystem::path& file_path) const;
// File persistence (only for programmatic creation/export - destroys formatting)
void persistEndpointConfigToFile(const EndpointConfig& config, const std::filesystem::path& file_path) const;
// Reload endpoint from disk (after external edit)
bool reloadEndpointConfig(const std::string& slug_or_path);
void printConfig() const;
static void printYamlNode(const YAML::Node& node, int indent = 0);
protected:
std::filesystem::path config_file;
YAML::Node config;
std::string project_name;
std::string project_description;
std::string cache_schema = "flapi";
std::string server_name;
int http_port = 8080;
std::unordered_map<std::string, ConnectionConfig> connections;
RateLimitConfig rate_limit_config;
bool auth_enabled;
AuthConfig global_auth_config;
std::vector<EndpointConfig> endpoints;
std::filesystem::path base_path;
DuckDBConfig duckdb_config;
TemplateConfig template_config;
HttpsConfig https_config;
GlobalHeartbeatConfig global_heartbeat_config;
DuckLakeConfig ducklake_config;
MCPConfig mcp_config;
StorageConfig storage_config;
bool telemetry_enabled = true;
ExtendedYamlParser yaml_parser;
// Extracted classes for delegation (Facade pattern)
std::unique_ptr<ConfigLoader> config_loader;
std::unique_ptr<EndpointRepository> endpoint_repository;
std::unique_ptr<ConfigValidator> config_validator;
std::unique_ptr<ConfigSerializer> config_serializer;
void parseConfig();
std::string getFullCacheSourcePath(const EndpointConfig& endpoint) const;
void parseMainConfig();
void parseConnections();
void parseRateLimitConfig();
void parseAuthConfig();
void parseDuckDBConfig();
void parseHttpsConfig();
void parseTemplateConfig();
void parseDuckLakeConfig();
void parseMCPConfig();
void parseStorageConfig();
void parseEndpointConfig(const std::filesystem::path& config_file);
void parseEndpointRequestFields(const YAML::Node& endpoint_config, EndpointConfig& endpoint);
void parseEndpointValidators(const YAML::Node& req, RequestFieldConfig& field);
void parseEndpointConnection(const YAML::Node& endpoint_config, EndpointConfig& endpoint);
void parseEndpointRateLimit(const YAML::Node& endpoint_config, EndpointConfig& endpoint);
void parseEndpointAuth(const YAML::Node& endpoint_config, EndpointConfig& endpoint);
OIDCConfig parseOIDCConfigNode(const YAML::Node& oidc_node, const std::string& log_prefix = "") const;
void parseEndpointCache(const YAML::Node& endpoint_config, const std::filesystem::path& endpoint_dir, EndpointConfig& endpoint);
void parseGlobalHeartbeatConfig();
void parseEndpointHeartbeat(const YAML::Node& endpoint_config, EndpointConfig& endpoint);
std::string makePathRelativeToBasePathIfNecessary(const std::string& value) const;
void validateConfig();
void validateEndpointConfig(const YAML::Node& endpoint_config, const std::string& file_path);
template<typename T>
T getValueOrThrow(const YAML::Node& node, const std::string& key, const std::string& yamlPath) const;
template<typename T>
T safeGet(const YAML::Node& node, const std::string& key, const std::string& path, const T& defaultValue) const;
template<typename T>
T safeGet(const YAML::Node& node, const std::string& key, const std::string& path) const;
public:
static std::string secretNameToTableName(const std::string& secret_name);
static std::string secretNameToSecretId(const std::string& secret_name);
static std::string createDefaultAuthInit(const std::string& secret_name,
const std::string& region,
const std::string& secret_id,
const std::string& secret_key);
};
class ConfigurationError : public std::runtime_error {
public:
ConfigurationError(const std::string& message, const std::string& yamlPath = "")
: std::runtime_error(formatMessage(message, yamlPath)) {}
private:
static std::string formatMessage(const std::string& message, const std::string& yamlPath) {
std::ostringstream oss;
oss << "Configuration error";
if (!yamlPath.empty()) {
oss << " at " << yamlPath;
}
oss << ": " << message;
return oss.str();
}
};
} // namespace flapi