// // License type: BSD 3-Clause License // License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE // // Created by Stephane Bourque on 2021-03-04. // Arilia Wireless Inc. // #include "RESTAPI_inventory_handler.h" #include "APConfig.h" #include "AutoDiscovery.h" #include "DeviceTypeCache.h" #include "RESTAPI/RESTAPI_db_helpers.h" #include "SerialNumberCache.h" #include "StorageService.h" #include "Tasks/VenueConfigUpdater.h" #include "framework/utils.h" #include "sdks/SDK_gw.h" #include "sdks/SDK_sec.h" namespace OpenWifi { void RESTAPI_inventory_handler::DoGet() { ProvObjects::InventoryTag Existing; std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER, ""); poco_debug(Logger(), fmt::format("{}: Retrieving inventory information.", SerialNumber)); if (SerialNumber.empty() || !DB_.GetRecord(RESTAPI::Protocol::SERIALNUMBER, SerialNumber, Existing)) { return NotFound(); } poco_debug(Logger(), fmt::format("{},{}: Retrieving inventory information.", Existing.serialNumber, Existing.info.id)); Poco::JSON::Object Answer; std::string Arg; if (GetBoolParameter("config", false)) { bool Explain = GetBoolParameter("explain", false); APConfig Device(SerialNumber, Existing.deviceType, Logger(), Explain); auto Configuration = Poco::makeShared(); if (Device.Get(Configuration)) { Answer.set("config", Configuration); if (Explain) Answer.set("explanation", Device.Explanation()); } else { Answer.set("config", "none"); } return ReturnObject(Answer); } else if (GetBoolParameter("firmwareOptions", false)) { ProvObjects::DeviceRules Rules; StorageService()->InventoryDB().EvaluateDeviceSerialNumberRules(SerialNumber, Rules); Answer.set("firmwareUpgrade", Rules.firmwareUpgrade); Answer.set("firmwareRCOnly", Rules.rcOnly == "yes"); return ReturnObject(Answer); } else if (GetBoolParameter("rrmSettings", false)) { ProvObjects::DeviceRules Rules; StorageService()->InventoryDB().EvaluateDeviceSerialNumberRules(SerialNumber, Rules); if (Rules.rrm == "no" || Rules.rrm == "inherit") { Answer.set("rrm", Rules.rrm); } else { ProvObjects::RRMDetails D; Poco::JSON::Parser P; try { auto Obj = P.parse(Rules.rrm).extract(); Answer.set("rrm", Obj); } catch (...) { Answer.set("rrm", "invalid"); } } return ReturnObject(Answer); } else if (GetBoolParameter("applyConfiguration", false)) { poco_debug(Logger(), fmt::format("{}: Retrieving configuration.", Existing.serialNumber)); auto Device = std::make_shared(SerialNumber, Existing.deviceType, Logger(), false); auto Configuration = Poco::makeShared(); Poco::JSON::Object ErrorsObj, WarningsObj; ProvObjects::InventoryConfigApplyResult Results; poco_debug(Logger(), fmt::format("{}: Computing configuration.", Existing.serialNumber)); if (Device->Get(Configuration)) { std::ostringstream OS; Configuration->stringify(OS); Results.appliedConfiguration = OS.str(); auto Response = Poco::makeShared(); poco_debug(Logger(), fmt::format("{}: Sending configuration push.", Existing.serialNumber)); if (SDK::GW::Device::Configure(this, SerialNumber, Configuration, Response)) { poco_debug(Logger(), fmt::format("{}: Sending configuration pushed.", Existing.serialNumber)); GetRejectedLines(Response, Results.warnings); Results.errorCode = 0; } else { poco_debug(Logger(), fmt::format("{}: Sending configuration failed.", Existing.serialNumber)); Results.errorCode = 1; } } else { poco_debug(Logger(), fmt::format("{}: Configuration is bad.", Existing.serialNumber)); Results.errorCode = 1; } Results.to_json(Answer); return ReturnObject(Answer); } else if (GetBoolParameter("resolveConfig", false)) { poco_debug(Logger(), fmt::format("{}: Retrieving configuration.", Existing.serialNumber)); auto Device = std::make_shared(SerialNumber, Existing.deviceType, Logger(), false); auto Configuration = Poco::makeShared(); Poco::JSON::Object ErrorsObj, WarningsObj; ProvObjects::InventoryConfigApplyResult Results; poco_debug(Logger(), Poco::format("{}: Computing configuration.", Existing.serialNumber)); if (Device->Get(Configuration)) { Answer.set("configuration", Configuration); } else { Answer.set("error", 1); } return ReturnObject(Answer); } else if (QB_.AdditionalInfo) { AddExtendedInfo(Existing, Answer); } Existing.to_json(Answer); ReturnObject(Answer); } void RESTAPI_inventory_handler::DoDelete() { ProvObjects::InventoryTag Existing; std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER, ""); if (SerialNumber.empty() || !DB_.GetRecord(RESTAPI::Protocol::SERIALNUMBER, SerialNumber, Existing)) { return NotFound(); } MoveUsage(StorageService()->PolicyDB(), DB_, Existing.managementPolicy, "", Existing.info.id); RemoveMembership(StorageService()->VenueDB(), &ProvObjects::Venue::configurations, Existing.venue, Existing.info.id); RemoveMembership(StorageService()->EntityDB(), &ProvObjects::Entity::configurations, Existing.entity, Existing.info.id); MoveUsage(StorageService()->LocationDB(), DB_, Existing.location, "", Existing.info.id); MoveUsage(StorageService()->ContactDB(), DB_, Existing.contact, "", Existing.info.id); if (!Existing.deviceConfiguration.empty()) { ProvObjects::DeviceConfiguration DC; if (StorageService()->ConfigurationDB().GetRecord("id", Existing.deviceConfiguration, DC)) { if (DC.subscriberOnly) StorageService()->ConfigurationDB().DeleteRecord("id", Existing.deviceConfiguration); else StorageService()->ConfigurationDB().DeleteInUse( "id", Existing.deviceConfiguration, DB_.Prefix(), Existing.info.id); } } MoveUsage(StorageService()->PolicyDB(), DB_, Existing.managementPolicy, "", Existing.info.id); MoveUsage(StorageService()->LocationDB(), DB_, Existing.location, "", Existing.info.id); MoveUsage(StorageService()->ContactDB(), DB_, Existing.contact, "", Existing.info.id); MoveUsage(StorageService()->ConfigurationDB(), DB_, Existing.deviceConfiguration, "", Existing.info.id); ManageMembership(StorageService()->EntityDB(), &ProvObjects::Entity::devices, Existing.entity, "", Existing.info.id); ManageMembership(StorageService()->VenueDB(), &ProvObjects::Venue::devices, Existing.venue, "", Existing.info.id); DB_.DeleteRecord("id", Existing.info.id); SerialNumberCache()->DeleteSerialNumber(SerialNumber); return OK(); } void RESTAPI_inventory_handler::DoPost() { std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER, ""); Poco::toLowerInPlace(SerialNumber); if (SerialNumber.empty()) { return BadRequest(RESTAPI::Errors::MissingSerialNumber); } if (!NormalizeMac(SerialNumber)) { return BadRequest(RESTAPI::Errors::InvalidSerialNumber); } if (DB_.Exists(RESTAPI::Protocol::SERIALNUMBER, SerialNumber)) { return BadRequest(RESTAPI::Errors::SerialNumberExists); } const auto &RawObject = ParsedBody_; ProvObjects::InventoryTag NewObject; if (!NewObject.from_json(RawObject)) { return BadRequest(RESTAPI::Errors::InvalidJSONDocument); } NormalizeMac(NewObject.serialNumber); if (SerialNumber != NewObject.serialNumber) { return BadRequest(RESTAPI::Errors::SerialNumberMismatch); } if ((RawObject->has("deviceRules") && !ValidDeviceRules(NewObject.deviceRules, *this))) { return; } if (!Provisioning::DeviceClass::Validate(NewObject.devClass.c_str())) { return BadRequest(RESTAPI::Errors::InvalidDeviceClass); } if (NewObject.devClass.empty()) { NewObject.devClass = Provisioning::DeviceClass::ANY; } if (!ProvObjects::CreateObjectInfo(RawObject, UserInfo_.userinfo, NewObject.info)) { return BadRequest(RESTAPI::Errors::NameMustBeSet); } if (NewObject.deviceType.empty() || !DeviceTypeCache()->IsAcceptableDeviceType(NewObject.deviceType)) { return BadRequest(RESTAPI::Errors::InvalidDeviceTypes); } if (OpenWifi::EntityDB::IsRoot(NewObject.entity) || (!NewObject.entity.empty() && !StorageService()->EntityDB().Exists("id", NewObject.entity))) { return BadRequest(RESTAPI::Errors::ValidNonRootUUID); } if (!NewObject.venue.empty() && !StorageService()->VenueDB().Exists("id", NewObject.venue)) { return BadRequest(RESTAPI::Errors::VenueMustExist); } if (!NewObject.venue.empty() && !NewObject.entity.empty()) { return BadRequest(RESTAPI::Errors::NotBoth); } if (!NewObject.location.empty() && !StorageService()->LocationDB().Exists("id", NewObject.location)) { return BadRequest(RESTAPI::Errors::LocationMustExist); } if (!NewObject.contact.empty() && !StorageService()->ContactDB().Exists("id", NewObject.contact)) { return BadRequest(RESTAPI::Errors::ContactMustExist); } if (!NewObject.deviceConfiguration.empty() && !StorageService()->ConfigurationDB().Exists("id", NewObject.deviceConfiguration)) { return BadRequest(RESTAPI::Errors::ConfigurationMustExist); } if (!NewObject.managementPolicy.empty() && !StorageService()->PolicyDB().Exists("id", NewObject.managementPolicy)) { return BadRequest(RESTAPI::Errors::UnknownManagementPolicyUUID); } std::vector Errors; auto ObjectsCreated = CreateObjects(NewObject, *this, Errors); if (!Errors.empty()) { return BadRequest(RESTAPI::Errors::ConfigBlockInvalid); } if (DB_.CreateRecord(NewObject)) { SDK::GW::Device::SetOwnerShip(this, SerialNumber, NewObject.entity, NewObject.venue, NewObject.subscriber); SerialNumberCache()->AddSerialNumber(SerialNumber, NewObject.deviceType); MoveUsage(StorageService()->PolicyDB(), DB_, "", NewObject.managementPolicy, NewObject.info.id); MoveUsage(StorageService()->LocationDB(), DB_, "", NewObject.location, NewObject.info.id); MoveUsage(StorageService()->ContactDB(), DB_, "", NewObject.contact, NewObject.info.id); MoveUsage(StorageService()->ConfigurationDB(), DB_, "", NewObject.deviceConfiguration, NewObject.info.id); ManageMembership(StorageService()->EntityDB(), &ProvObjects::Entity::devices, "", NewObject.entity, NewObject.info.id); ManageMembership(StorageService()->VenueDB(), &ProvObjects::Venue::devices, "", NewObject.venue, NewObject.info.id); ProvObjects::InventoryTag NewTag; DB_.GetRecord("id", NewObject.info.id, NewTag); Poco::JSON::Object Answer; NewTag.to_json(Answer); return ReturnObject(Answer); } InternalError(RESTAPI::Errors::RecordNotCreated); } void RESTAPI_inventory_handler::DoPut() { std::string SerialNumber = GetBinding(RESTAPI::Protocol::SERIALNUMBER, ""); if (SerialNumber.empty() || !Utils::ValidSerialNumber(SerialNumber)) { return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); } ProvObjects::InventoryTag Existing; if (SerialNumber.empty() || !DB_.GetRecord(RESTAPI::Protocol::SERIALNUMBER, SerialNumber, Existing)) { return NotFound(); } std::string previous_venue = Existing.venue; auto RemoveSubscriber = GetParameter("removeSubscriber"); if (!RemoveSubscriber.empty()) { if (Existing.subscriber == RemoveSubscriber) { poco_information(Logger(), fmt::format("{}: removing subscriber ({})", SerialNumber, RemoveSubscriber)); ProvObjects::DeviceConfiguration DC; if (StorageService()->ConfigurationDB().GetRecord( "id", Existing.deviceConfiguration, DC)) { poco_information(Logger(), fmt::format("{}: removing configuration for subscriber ({})", SerialNumber, RemoveSubscriber)); if (DC.subscriberOnly) { if (!StorageService()->ConfigurationDB().DeleteRecord( "id", Existing.deviceConfiguration)) { poco_debug(Logger(), "Could not delete the subscriber configuration"); } } else { poco_debug(Logger(), "Configurations is not for a subscriber."); } Existing.deviceConfiguration = ""; } Existing.subscriber = ""; Poco::JSON::Object state; state.set("date", Utils::Now()); state.set("method", "auto-discovery"); state.set("last-operation", "returned to inventory"); std::ostringstream OO; state.stringify(OO); Existing.state = OO.str(); StorageService()->InventoryDB().UpdateRecord("id", Existing.info.id, Existing); RemoveMembership(StorageService()->EntityDB(), &ProvObjects::Entity::devices, "id", Existing.info.id); Poco::JSON::Object Answer; Existing.to_json(Answer); SDK::GW::Device::SetSubscriber(nullptr, SerialNumber, ""); return ReturnObject(Answer); } else { poco_information(Logger(), fmt::format("{}: wrong subscriber ({})", SerialNumber, RemoveSubscriber)); } return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); } const auto &RawObject = ParsedBody_; ProvObjects::InventoryTag NewObject; if (!NewObject.from_json(RawObject)) { return BadRequest(RESTAPI::Errors::InvalidJSONDocument); } if ((RawObject->has("deviceRules") && !ValidDeviceRules(NewObject.deviceRules, *this))) { return; } if (!Provisioning::DeviceClass::Validate(NewObject.devClass.c_str())) { return BadRequest(RESTAPI::Errors::InvalidDeviceClass); } if (!NewObject.deviceType.empty()) { if (!DeviceTypeCache()->IsAcceptableDeviceType(NewObject.deviceType)) { return BadRequest(RESTAPI::Errors::InvalidDeviceTypes); } } if (!UpdateObjectInfo(RawObject, UserInfo_.userinfo, Existing.info)) { return BadRequest(RESTAPI::Errors::NameMustBeSet); } if (RawObject->has("deviceRules")) Existing.deviceRules = NewObject.deviceRules; std::string FromPolicy, ToPolicy; if (!CreateMove(RawObject, "managementPolicy", &InventoryDB::RecordName::managementPolicy, Existing, FromPolicy, ToPolicy, StorageService()->PolicyDB())) return BadRequest(RESTAPI::Errors::EntityMustExist); std::string FromEntity, ToEntity; if (!CreateMove(RawObject, "entity", &InventoryDB::RecordName::entity, Existing, FromEntity, ToEntity, StorageService()->EntityDB())) return BadRequest(RESTAPI::Errors::EntityMustExist); std::string FromVenue, ToVenue; if (!CreateMove(RawObject, "venue", &InventoryDB::RecordName::venue, Existing, FromVenue, ToVenue, StorageService()->VenueDB())) return BadRequest(RESTAPI::Errors::VenueMustExist); std::string FromLocation, ToLocation; if (!CreateMove(RawObject, "location", &InventoryDB::RecordName::location, Existing, FromLocation, ToLocation, StorageService()->LocationDB())) return BadRequest(RESTAPI::Errors::VenueMustExist); std::string FromContact, ToContact; if (!CreateMove(RawObject, "contact", &InventoryDB::RecordName::contact, Existing, FromContact, ToContact, StorageService()->ContactDB())) return BadRequest(RESTAPI::Errors::VenueMustExist); std::string FromConfiguration, ToConfiguration; if (!CreateMove(RawObject, "deviceConfiguration", &InventoryDB::RecordName::deviceConfiguration, Existing, FromConfiguration, ToConfiguration, StorageService()->ConfigurationDB())) return BadRequest(RESTAPI::Errors::ConfigurationMustExist); std::string NewSubScriber; if (AssignIfPresent(RawObject, "subscriber", NewSubScriber)) { if (!NewSubScriber.empty()) { if (NewSubScriber != Existing.subscriber) { SecurityObjects::UserInfo U; if (SDK::Sec::Subscriber::Get(this, NewSubScriber, U)) { Existing.subscriber = NewSubScriber; } else { return BadRequest(RESTAPI::Errors::SubscriberMustExist); } } } else { Existing.subscriber = ""; } } AssignIfPresent(RawObject, "doNotAllowOverrides", Existing.doNotAllowOverrides); if (RawObject->has("devClass") && NewObject.devClass != Existing.devClass) { Existing.devClass = NewObject.devClass; } if (RawObject->has("state") && NewObject.state != Existing.state) { Existing.state = NewObject.state; } std::vector Errors; auto ObjectsCreated = CreateObjects(NewObject, *this, Errors); if (!Errors.empty()) { return BadRequest(RESTAPI::Errors::ConfigBlockInvalid); } if (!ObjectsCreated.empty()) { auto it = ObjectsCreated.find("configuration"); if (it != ObjectsCreated.end()) { FromConfiguration = ""; ToConfiguration = it->second; Existing.deviceConfiguration = ToConfiguration; } } if (StorageService()->InventoryDB().UpdateRecord("id", Existing.info.id, Existing)) { MoveUsage(StorageService()->PolicyDB(), DB_, FromPolicy, ToPolicy, Existing.info.id); MoveUsage(StorageService()->LocationDB(), DB_, FromLocation, ToLocation, Existing.info.id); MoveUsage(StorageService()->ContactDB(), DB_, FromContact, ToContact, Existing.info.id); MoveUsage(StorageService()->ConfigurationDB(), DB_, FromConfiguration, ToConfiguration, Existing.info.id); ManageMembership(StorageService()->EntityDB(), &ProvObjects::Entity::devices, FromEntity, ToEntity, Existing.info.id); ManageMembership(StorageService()->VenueDB(), &ProvObjects::Venue::devices, FromVenue, ToVenue, Existing.info.id); SDK::GW::Device::SetOwnerShip(this, SerialNumber, Existing.entity, Existing.venue, Existing.subscriber); // Attempt an automatic config push when the venue is set and different than what is // in DB. poco_information(Logger(), fmt::format("New Venue {} Old Venue {}", NewObject.venue, previous_venue)); if (!NewObject.venue.empty() && NewObject.venue != previous_venue) { ComputeAndPushConfig(SerialNumber, NewObject.deviceType, Logger()); } ProvObjects::InventoryTag NewObjectCreated; DB_.GetRecord("id", Existing.info.id, NewObjectCreated); Poco::JSON::Object Answer; NewObject.to_json(Answer); return ReturnObject(Answer); } InternalError(RESTAPI::Errors::RecordNotUpdated); } } // namespace OpenWifi