From aaf4022028447cc10e9ced785b7a50f7762cc16c Mon Sep 17 00:00:00 2001 From: kishorenc Date: Wed, 13 May 2020 18:20:07 +0530 Subject: [PATCH] Expose API key management, aliasing and overrides. --- include/core_api.h | 50 +++++- src/core_api.cpp | 307 ++++++++++++++++++++++++++++++++++ src/main/typesense_server.cpp | 15 ++ 3 files changed, 366 insertions(+), 6 deletions(-) diff --git a/include/core_api.h b/include/core_api.h index b16565e9..3fef072a 100644 --- a/include/core_api.h +++ b/include/core_api.h @@ -4,22 +4,20 @@ bool handle_authentication(http_req& req, const route_path& rpath, const std::string& auth_key); +// Collections + bool get_collections(http_req & req, http_res & res); bool post_create_collection(http_req & req, http_res & res); bool del_drop_collection(http_req & req, http_res & res); -bool get_debug(http_req & req, http_res & res); +bool get_collection_summary(http_req & req, http_res & res); -bool get_health(http_req & req, http_res & res); - -bool get_metrics_json(http_req & req, http_res & res); +// Documents bool get_search(http_req & req, http_res & res); -bool get_collection_summary(http_req & req, http_res & res); - bool get_export_documents(http_req & req, http_res & res); bool post_add_document(http_req & req, http_res & res); @@ -30,6 +28,46 @@ bool get_fetch_document(http_req & req, http_res & res); bool del_remove_document(http_req & req, http_res & res); +// Alias + +bool get_alias(http_req & req, http_res & res); + +bool get_aliases(http_req & req, http_res & res); + +bool put_upsert_alias(http_req & req, http_res & res); + +bool del_alias(http_req & req, http_res & res); + +// Overrides + +bool get_overrides(http_req & req, http_res & res); + +bool get_override(http_req & req, http_res & res); + +bool put_override(http_req & req, http_res & res); + +bool del_override(http_req & req, http_res & res); + +// Keys + +bool get_keys(http_req & req, http_res & res); + +bool post_create_key(http_req & req, http_res & res); + +bool get_key(http_req & req, http_res & res); + +bool del_key(http_req & req, http_res & res); + +// Metrics + +bool get_debug(http_req & req, http_res & res); + +bool get_health(http_req & req, http_res & res); + +bool get_metrics_json(http_req & req, http_res & res); + +// Misc helpers + bool collection_export_handler(http_req* req, http_res* res, void* data); bool async_write_request(void *data); diff --git a/src/core_api.cpp b/src/core_api.cpp index 7f1f2d21..e0b7907d 100644 --- a/src/core_api.cpp +++ b/src/core_api.cpp @@ -603,6 +603,313 @@ bool del_remove_document(http_req & req, http_res & res) { return true; } + +bool get_aliases(http_req & req, http_res & res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + const spp::sparse_hash_map & symlinks = collectionManager.get_symlinks(); + nlohmann::json res_json = nlohmann::json::object(); + res_json["aliases"] = nlohmann::json::array(); + + for(const auto & symlink_collection: symlinks) { + nlohmann::json symlink; + symlink["name"] = symlink_collection.first; + symlink["collection_name"] = symlink_collection.second; + res_json["aliases"].push_back(symlink); + } + + res.set_200(res_json.dump()); + return true; +} + +bool get_alias(http_req & req, http_res & res) { + const std::string & alias = req.params["alias"]; + CollectionManager & collectionManager = CollectionManager::get_instance(); + Option collection_name_op = collectionManager.resolve_symlink(alias); + + if(!collection_name_op.ok()) { + res.set_404(); + return false; + } + + nlohmann::json res_json; + res_json["name"] = alias; + res_json["collection_name"] = collection_name_op.get(); + + res.set_200(res_json.dump()); + return true; +} + +bool put_upsert_alias(http_req & req, http_res & res) { + nlohmann::json req_json; + + try { + req_json = nlohmann::json::parse(req.body); + } catch(const std::exception& e) { + LOG(ERROR) << "JSON error: " << e.what(); + res.set_400("Bad JSON."); + return false; + } + + CollectionManager & collectionManager = CollectionManager::get_instance(); + const std::string & alias = req.params["alias"]; + + const char* COLLECTION_NAME = "collection_name"; + + if(req_json.count(COLLECTION_NAME) == 0) { + res.set_400(std::string("Parameter `") + COLLECTION_NAME + "` is required."); + return false; + } + + Option success_op = collectionManager.upsert_symlink(alias, req_json[COLLECTION_NAME]); + if(!success_op.ok()) { + res.set_500(success_op.error()); + return false; + } + + req_json["name"] = alias; + res.set_200(req_json.dump()); + return true; +} + +bool del_alias(http_req & req, http_res & res) { + const std::string & alias = req.params["alias"]; + CollectionManager & collectionManager = CollectionManager::get_instance(); + + Option collection_name_op = collectionManager.resolve_symlink(alias); + if(!collection_name_op.ok()) { + res.set_404(); + return false; + } + + Option delete_op = collectionManager.delete_symlink(alias); + + if(!delete_op.ok()) { + res.set_500(delete_op.error()); + return false; + } + + nlohmann::json res_json; + res_json["name"] = alias; + res_json["collection_name"] = collection_name_op.get(); + res.set_200(res_json.dump()); + return true; +} + +bool get_overrides(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + Collection *collection = collectionManager.get_collection(req.params["collection"]); + + if(collection == nullptr) { + res.set_404(); + return false; + } + + nlohmann::json res_json; + res_json["overrides"] = nlohmann::json::array(); + + std::map overrides = collection->get_overrides(); + for(const auto & kv: overrides) { + nlohmann::json override = kv.second.to_json(); + res_json["overrides"].push_back(override); + } + + res.set_200(res_json.dump()); + return true; +} + +bool get_override(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + Collection *collection = collectionManager.get_collection(req.params["collection"]); + + if(collection == nullptr) { + res.set_404(); + return false; + } + + std::string override_id = req.params["id"]; + + std::map overrides = collection->get_overrides(); + + if(overrides.count(override_id) != 0) { + nlohmann::json override = overrides[override_id].to_json(); + res.set_200(override.dump()); + return false; + } + + res.set_404(); + return true; +} + +bool put_override(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + Collection *collection = collectionManager.get_collection(req.params["collection"]); + + std::string override_id = req.params["id"]; + + if(collection == nullptr) { + res.set_404(); + return false; + } + + nlohmann::json req_json; + + try { + req_json = nlohmann::json::parse(req.body); + } catch(const std::exception& e) { + LOG(ERROR) << "JSON error: " << e.what(); + res.set_400("Bad JSON."); + return false; + } + + // validate format of req_json + if( + !req_json.is_object() || + (req_json.count("rule") == 0) || + (req_json["rule"].count("query") == 0 || req_json["rule"].count("match") == 0) || + (req_json.count("includes") == 0 && req_json.count("excludes") == 0) + ) { + res.set_400("Bad JSON."); + return false; + } + + req_json["id"] = override_id; + + override_t override(req_json); + Option add_op = collection->add_override(override); + + if(!add_op.ok()) { + res.set(add_op.code(), add_op.error()); + return false; + } + + res.set_200(req_json.dump()); + return true; +} + +bool del_override(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + Collection *collection = collectionManager.get_collection(req.params["collection"]); + + if(collection == nullptr) { + res.set_404(); + return false; + } + + Option rem_op = collection->remove_override(req.params["id"]); + if(!rem_op.ok()) { + res.set(rem_op.code(), rem_op.error()); + return false; + } + + nlohmann::json res_json; + res_json["id"] = req.params["id"]; + + res.set_200(res_json.dump()); + return true; +} + +bool get_keys(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + AuthManager &auth_manager = collectionManager.getAuthManager(); + + const Option>& keys_op = auth_manager.list_keys(); + if(!keys_op.ok()) { + res.set(keys_op.code(), keys_op.error()); + return false; + } + + nlohmann::json res_json; + res_json["keys"] = nlohmann::json::array(); + + for(const auto & key: keys_op.get()) { + nlohmann::json key_obj = key.to_json(); + res_json["keys"].push_back(key_obj); + } + + res.set_200(res_json.dump()); + return true; +} + +bool post_create_key(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + AuthManager &auth_manager = collectionManager.getAuthManager(); + + nlohmann::json req_json; + + try { + req_json = nlohmann::json::parse(req.body); + } catch(const std::exception& e) { + LOG(ERROR) << "JSON error: " << e.what(); + res.set_400("Bad JSON."); + return false; + } + + + const Option& validate_op = api_key_t::validate(req_json); + if(!validate_op.ok()) { + res.set(validate_op.code(), validate_op.error()); + return false; + } + + const std::string &rand_key = StringUtils::randstring(AuthManager::KEY_LEN, req.seed); + + api_key_t api_key( + rand_key, + req_json["description"].get(), + req_json["actions"].get>(), + req_json["collections"].get>() + ); + + const Option& api_key_op = auth_manager.create_key(api_key); + if(!api_key_op.ok()) { + res.set(api_key_op.code(), api_key_op.error()); + return false; + } + + res.set(201, api_key_op.get().to_json().dump()); + return true; +} + +bool get_key(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + AuthManager &auth_manager = collectionManager.getAuthManager(); + + const std::string& key_id_str = req.params["id"]; + uint32_t key_id = (uint32_t) std::stol(key_id_str); + + const Option& key_op = auth_manager.get_key(key_id); + + if(!key_op.ok()) { + res.set(key_op.code(), key_op.error()); + return false; + } + + nlohmann::json res_json = key_op.get().to_json(); + res.set_200(res_json.dump()); + return true; +} + +bool del_key(http_req &req, http_res &res) { + CollectionManager & collectionManager = CollectionManager::get_instance(); + AuthManager &auth_manager = collectionManager.getAuthManager(); + + const std::string& key_id_str = req.params["id"]; + uint32_t key_id = (uint32_t) std::stol(key_id_str); + + const Option &del_op = auth_manager.remove_key(key_id); + + if(!del_op.ok()) { + res.set(del_op.code(), del_op.error()); + return false; + } + + nlohmann::json res_json; + res_json["id"] = req.params["id"]; + + res.set_200(res_json.dump()); + return true; +} + bool async_write_request(void *data) { //LOG(INFO) << "async_write_request called"; AsyncIndexArg* index_arg = static_cast(data); diff --git a/src/main/typesense_server.cpp b/src/main/typesense_server.cpp index 7281de86..5a3ef7d5 100644 --- a/src/main/typesense_server.cpp +++ b/src/main/typesense_server.cpp @@ -19,6 +19,21 @@ void master_server_routes() { server->get("/collections/:collection/documents/:id", get_fetch_document); server->del("/collections/:collection/documents/:id", del_remove_document); + server->get("/collections/:collection/overrides", get_overrides); + server->get("/collections/:collection/overrides/:id", get_override); + server->put("/collections/:collection/overrides/:id", put_override); + server->del("/collections/:collection/overrides/:id", del_override); + + server->get("/aliases", get_aliases); + server->get("/aliases/:alias", get_alias); + server->put("/aliases/:alias", put_upsert_alias); + server->del("/aliases/:alias", del_alias); + + server->get("/keys", get_keys); + server->get("/keys/:id", get_key); + server->post("/keys", post_create_key); + server->del("/keys/:id", del_key); + // meta server->get("/metrics.json", get_metrics_json); server->get("/debug", get_debug);