// // Created by stephane bourque on 2021-10-18. // #pragma once #include "RESTObjects/RESTAPI_ProvObjects.h" #include "StorageService.h" #include "framework/MicroService.h" #include "framework/ConfigurationValidator.h" #include "sdks/SDK_sec.h" #include "Poco/StringTokenizer.h" #include "libs/croncpp.h" namespace OpenWifi { inline static void AddInfoBlock(const ProvObjects::ObjectInfo & O, Poco::JSON::Object &J) { J.set("name", O.name); J.set("description", O.description); J.set("id", O.id); } template void Extend_entity(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.entity.empty()) { Poco::JSON::Object EntObj; ProvObjects::Entity Entity; if(StorageService()->EntityDB().GetRecord("id",T.entity,Entity)) { AddInfoBlock(Entity.info, EntObj); } EI.set("entity",EntObj); } } } template void Extend_entity(Ts... args) { static_assert( sizeof...(args) == 2); } template void Extend_managementPolicy(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.managementPolicy.empty()) { Poco::JSON::Object PolObj; ProvObjects::ManagementPolicy Policy; if(StorageService()->PolicyDB().GetRecord("id",T.managementPolicy,Policy)) { AddInfoBlock(Policy.info, PolObj); } EI.set("managementPolicy",PolObj); } } } template void Extend_managementPolicy(Ts... args) { static_assert( sizeof...(args) == 2); } template void Extend_venue(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.venue.empty()) { Poco::JSON::Object VenObj; ProvObjects::Venue Venue; if(StorageService()->VenueDB().GetRecord("id",T.venue,Venue)) { AddInfoBlock(Venue.info, VenObj); } EI.set("venue",VenObj); } } } template void Extend_venue(Ts... args) { static_assert( sizeof...(args) == 2); } template void Extend_contact(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.contact.empty()) { Poco::JSON::Object ConObj; ProvObjects::Contact Contact; if(StorageService()->ContactDB().GetRecord("id",T.contact,Contact)) { AddInfoBlock(Contact.info, ConObj); } EI.set("contact",ConObj); } } } template void Extend_contact(Ts... args) { static_assert( sizeof...(args) == 2); } template void Extend_location(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.location.empty()) { Poco::JSON::Object LocObj; ProvObjects::Location Location; if(StorageService()->LocationDB().GetRecord("id",T.location,Location)) { AddInfoBlock(Location.info, LocObj); } EI.set("location",LocObj); } } } template void Extend_location(Ts... args) { static_assert( sizeof...(args) == 2); } template void Extend_deviceConfiguration(const R &T, Poco::JSON::Object &EI ) { if constexpr(std::is_same_v) { if(!T.deviceConfiguration.empty()) { Poco::JSON::Object DevObj; ProvObjects::DeviceConfiguration DevConf; if(StorageService()->ConfigurationDB().GetRecord("id",T.deviceConfiguration,DevConf)) { AddInfoBlock(DevConf.info, DevObj); } EI.set("deviceConfiguration",DevObj); } } if constexpr(std::is_same_v) { if(!T.deviceConfiguration.empty()) { Poco::JSON::Array ObjArr; ProvObjects::DeviceConfiguration DevConf; for(const auto &i:T.deviceConfiguration) { if(StorageService()->ConfigurationDB().GetRecord("id",i,DevConf)) { Poco::JSON::Object InnerObj; AddInfoBlock(DevConf.info, InnerObj); ObjArr.add(InnerObj); } } EI.set("deviceConfiguration",ObjArr); } } } template void Extend_deviceConfiguration(Ts... args) { static_assert( sizeof...(args) == 2); } template bool AddExtendedInfo(const R & T, Poco::JSON::Object &O) { Poco::JSON::Object EI; Extend_entity(T,EI); Extend_deviceConfiguration(T,EI); Extend_location(T,EI); Extend_contact(T,EI); Extend_venue(T,EI); Extend_managementPolicy(T,EI); O.set("extendedInfo", EI); return true; } template void MakeJSONObjectArray(const char * ArrayName, const std::vector & V, RESTAPIHandler & R) { Poco::JSON::Array ObjArray; for(const auto &i:V) { Poco::JSON::Object Obj; i.to_json(Obj); if(R.NeedAdditionalInfo()) AddExtendedInfo(i,Obj); ObjArray.add(Obj); } Poco::JSON::Object Answer; Answer.set(ArrayName,ObjArray); return R.ReturnObject(Answer); } inline static bool is_uuid(const std::string &u) { return u.find('-') != std::string::npos; } template void ReturnRecordList(const char *ArrayName,DB & DBInstance, RESTAPIHandler & R) { Poco::JSON::Array ObjArr; for(const auto &i:R.SelectedRecords()) { ProvObjects::InventoryTag E; if(DBInstance.GetRecord(is_uuid(i) ? "id" : "serialNumber",i,E)) { Poco::JSON::Object Obj; E.to_json(Obj); if(R.NeedAdditionalInfo()) AddExtendedInfo(E,Obj); ObjArr.add(Obj); } else { return R.BadRequest(RESTAPI::Errors::UnknownId); } } Poco::JSON::Object Answer; Answer.set(ArrayName, ObjArr); return R.ReturnObject(Answer); } template void ReturnRecordList(const char *ArrayName,DB & DBInstance, RESTAPIHandler & R) { Poco::JSON::Array ObjArr; for(const auto &i:R.SelectedRecords()) { Record E; if(DBInstance.GetRecord("id",i,E)) { Poco::JSON::Object Obj; E.to_json(Obj); if(R.NeedAdditionalInfo()) AddExtendedInfo(E,Obj); ObjArr.add(Obj); } else { return R.BadRequest(RESTAPI::Errors::UnknownId); } } Poco::JSON::Object Answer; Answer.set(ArrayName, ObjArr); return R.ReturnObject(Answer); } inline bool NormalizeMac(std::string & Mac) { Poco::replaceInPlace(Mac,":",""); Poco::replaceInPlace(Mac,"-",""); if(Mac.size()!=12) return false; for(const auto &i:Mac) { if(!std::isxdigit(i)) return false; } Poco::toLowerInPlace(Mac); return true; } typedef std::tuple triplet_t; inline void AddLocationTriplet(const std::string &id, std::vector & IDs) { ProvObjects::Location L; if(StorageService()->LocationDB().GetRecord("id",id,L)) { IDs.emplace_back(std::make_tuple(L.info.name,L.info.description,L.info.id)); } } inline void AddLocationTriplet(const std::vector &id, std::vector & IDs) { for(const auto &i:id) AddLocationTriplet(i,IDs); } inline void GetLocationsForEntity(const std::string &ID, std::vector & IDs) { ProvObjects::Entity Existing; if(StorageService()->EntityDB().template GetRecord("id",ID,Existing)) { if(!Existing.locations.empty()) { AddLocationTriplet(Existing.locations,IDs); } if(!Existing.parent.empty()) { GetLocationsForEntity(Existing.parent,IDs); } if(ID==EntityDB::RootUUID()) return; } } inline void GetLocationsForVenue(const std::string &ID, std::vector & IDs) { ProvObjects::Venue Existing; if(StorageService()->VenueDB().template GetRecord("id",ID,Existing)) { if(!Existing.parent.empty()) { GetLocationsForVenue(Existing.parent,IDs); } ProvObjects::Entity E; if(StorageService()->EntityDB().GetRecord("id", Existing.entity, E)) { AddLocationTriplet(E.locations,IDs); } return; } } template void ListHandler(const char *BlockName,DB & DBInstance, RESTAPIHandler & R) { auto Entity = R.GetParameter("entity", ""); auto Venue = R.GetParameter("venue", ""); typedef typename DB::RecordVec RecVec; typedef typename DB::RecordName RecType; if constexpr(std::is_same_v) { auto LocationsForVenue = R.GetParameter("locationsForVenue",""); if(!LocationsForVenue.empty()) { std::vector IDs; GetLocationsForVenue(LocationsForVenue,IDs); Poco::JSON::Array A; for(const auto &[name,description,uuid]:IDs) { Poco::JSON::Object O; O.set("name", name); O.set("description",description); O.set("uuid",uuid); A.add(O); } Poco::JSON::Object Answer; Answer.set("locations",A); return R.ReturnObject(Answer); } } if(!R.QB_.Select.empty()) { return ReturnRecordList(BlockName, DBInstance, R); } if(!Entity.empty()) { RecVec Entries; DBInstance.GetRecords(R.QB_.Offset,R.QB_.Limit,Entries," entity=' " + Entity +"'"); if(R.QB_.CountOnly) return R.ReturnCountOnly(Entries.size()); return MakeJSONObjectArray(BlockName, Entries, R); } if(!Venue.empty()) { RecVec Entries; DBInstance.GetRecords(R.QB_.Offset,R.QB_.Limit,Entries," venue=' " + Venue +"'"); if(R.QB_.CountOnly) return R.ReturnCountOnly(Entries.size()); return MakeJSONObjectArray(BlockName, Entries, R); } else if(R.QB_.CountOnly) { Poco::JSON::Object Answer; auto C = DBInstance.Count(); return R.ReturnCountOnly(C); } else { RecVec Entries; DBInstance.GetRecords(R.QB_.Offset,R.QB_.Limit,Entries); return MakeJSONObjectArray(BlockName, Entries, R); } } template void ListHandlerForOperator(const char *BlockName,db_type & DB, RESTAPIHandler & R, const Types::UUID_t & OperatorId, const Types::UUID_t & subscriberId="") { typedef typename db_type::RecordVec RecVec; typedef typename db_type::RecordName RecType; auto whereClause = subscriberId.empty() ? fmt::format(" operatorId='{}'", OperatorId) : fmt::format(" operatorId='{}' and subscriberId='{}' ", OperatorId, subscriberId); if(R.QB_.CountOnly) { auto Count = DB.Count( whereClause ); return R.ReturnCountOnly(Count); } if(!R.QB_.Select.empty()) { return ReturnRecordList(BlockName, DB, R); } RecVec Entries; DB.GetRecords(R.QB_.Offset,R.QB_.Limit,Entries,whereClause); return MakeJSONObjectArray(BlockName, Entries, R); } template void MoveUsage(db_type &DB_InUse, ObjectDB & DB, const std::string & From, const std::string & To, const std::string &Id) { if(From!=To) { if(!From.empty()) DB_InUse.DeleteInUse("id",From,DB.Prefix(),Id); if(!To.empty()) DB_InUse.AddInUse("id",To,DB.Prefix(),Id); } } template void MoveUsage(db_type &DB_InUse, ObjectDB & DB, const Types::UUIDvec_t & From, const Types::UUIDvec_t & To, const std::string &Id) { if(From!=To) { if(!From.empty()) { for(const auto &i:From) DB_InUse.DeleteInUse("id", i, DB.Prefix(), Id); } if(!To.empty()) { for(const auto &i:To) DB_InUse.AddInUse("id", i, DB.Prefix(), Id); } } } template void MoveChild(db_type &DB, const std::string & Parent, const std::string & Child, const std::string &Id) { if(Parent!=Child) { if(!Parent.empty()) DB.InUse.DeleteInUse("id",Parent,Id); if(!Child.empty()) DB.AddInUse("id",Child,Id); } } template void RemoveMembership( db_type & DB, Member T, const std::string & Obj, const std::string &Id) { if(!Obj.empty()) DB.ManipulateVectorMember(T, "id", Obj, Id, false); } template void AddMembership( db_type & DB, Member T, const std::string & Obj, const std::string &Id) { if(!Obj.empty()) DB.ManipulateVectorMember(T, "id", Obj, Id, true); } template void ManageMembership( db_type & DB, Member T, const std::string & From, const std::string & To, const std::string &Id) { RemoveMembership(DB,T,From,Id); AddMembership(DB,T,To,Id); } template void ManageMembership( db_type & DB, Member T, const Types::UUIDvec_t & From, const Types::UUIDvec_t & To, const std::string &Id) { if(From!=To) { for (const auto &i: From) { RemoveMembership(DB, T, i, Id); } for (const auto &i: To) { AddMembership(DB, T, i, Id); } } } template bool CreateMove(const Poco::JSON::Object::Ptr & RawObj, const char *fieldname, Member T, Rec & Existing, std::string &From, std::string &To, db_type & TheDB) { if(RawObj->has(fieldname)) { From = Existing.*T; To = RawObj->get(fieldname).toString(); if(!To.empty() && !TheDB.Exists("id",To)) return false; Existing.*T=To; } return true; } inline std::string FindParentEntity(const ProvObjects::Venue &V) { if(V.parent.empty()) return V.entity; ProvObjects::Venue P; if(StorageService()->VenueDB().GetRecord("id",V.parent,P)) return FindParentEntity(P); return EntityDB::RootUUID(); } inline bool ValidateConfigBlock(const ProvObjects::DeviceConfiguration &Config, RESTAPI::Errors::msg & Error) { static const std::vector SectionNames{ "globals", "interfaces", "metrics", "radios", "services", "unit", "definitions", "ethernet", "switch", "config-raw", "third-party" }; for(const auto &i:Config.configuration) { Poco::JSON::Parser P; if(i.name.empty()) { std::cout << "Name is empty" << std::endl; Error = RESTAPI::Errors::NameMustBeSet; return false; } try { auto Blocks = P.parse(i.configuration).extract(); auto N = Blocks->getNames(); for (const auto &j: N) { if (std::find(SectionNames.cbegin(), SectionNames.cend(), j) == SectionNames.cend()) { Error = RESTAPI::Errors::UnknownConfigurationSection; return false; } } } catch (const Poco::JSON::JSONException &E ) { Error = RESTAPI::Errors::InvalidJSONDocument; return false; } try { std::string ErrorText; if (ValidateUCentralConfiguration(i.configuration, ErrorText)) { // std::cout << "Block: " << i.name << " is valid" << std::endl; } else { Error = RESTAPI::Errors::ConfigBlockInvalid ; return false; } } catch(...) { std::cout << "Exception in validation" << std::endl; return false; } } return true; } template std::map CreateObjects(Type & NewObject, RESTAPIHandler & R, RESTAPI::Errors::msg & Error) { std::map Result; auto createObjects = R.GetParameter("createObjects",""); if(!createObjects.empty()) { std::cout << "createObjects: " << createObjects << std::endl; Poco::JSON::Parser P; auto Objects = P.parse(createObjects).extract(); if(Objects->isArray("objects")) { auto ObjectsArray = Objects->getArray("objects"); for(const auto &i:*ObjectsArray) { auto Object = i.extract(); if (Object->has("location")) { auto LocationDetails = Object->get("location").extract(); ProvObjects::Location LC; if (LC.from_json(LocationDetails)) { if constexpr(std::is_same_v) { std::cout << "Location decoded: " << LC.info.name << std::endl; std::string ParentEntity = FindParentEntity(NewObject); ProvObjects::CreateObjectInfo(R.UserInfo_.userinfo, LC.info); LC.entity = ParentEntity; if (StorageService()->LocationDB().CreateRecord(LC)) { NewObject.location = LC.info.id; AddMembership(StorageService()->EntityDB(), &ProvObjects::Entity::locations, ParentEntity, LC.info.id); Result["location"] = LC.info.id; } } if constexpr(std::is_same_v) { std::cout << "Location decoded: " << LC.info.name << std::endl; std::string ParentEntity = FindParentEntity(NewObject); ProvObjects::CreateObjectInfo(R.UserInfo_.userinfo, LC.info); LC.entity = ParentEntity; if (StorageService()->LocationDB().CreateRecord(LC)) { NewObject.location = LC.info.id; AddMembership(StorageService()->EntityDB(), &ProvObjects::Entity::locations, ParentEntity, LC.info.id); Result["location"] = LC.info.id; } } } else { Error = RESTAPI::Errors::InvalidJSONDocument; break; } } else if (Object->has("contact")) { auto ContactDetails = Object->get("contact").extract(); ProvObjects::Contact CC; if (CC.from_json(ContactDetails)) { std::cout << "contact decoded: " << CC.info.name << std::endl; } else { std::cout << "contact not decoded." << std::endl; } } else if (Object->has("configuration")) { auto ConfigurationDetails = Object->get("configuration").template extract(); ProvObjects::DeviceConfiguration DC; if(DC.from_json(ConfigurationDetails)) { if constexpr(std::is_same_v) { if(!ValidateConfigBlock(DC,Error)) { break; } std::cout << "Configuration decoded: " << DC.info.name << std::endl; ProvObjects::CreateObjectInfo(R.UserInfo_.userinfo, DC.info); if (StorageService()->ConfigurationDB().CreateRecord(DC)) { NewObject.deviceConfiguration = DC.info.id; Result["configuration"] = DC.info.id; } } } else { Error = RESTAPI::Errors::InvalidJSONDocument; break; } } } } } return Result; } inline bool ValidSchedule(const std::string &v) { try { auto cron = cron::make_cron(v); return true; } catch (cron::bad_cronexpr const & ex) { } return false; } inline bool ValidRRM(const std::string &v) { if((v=="no") || (v=="inherit")) return true; try { Poco::JSON::Parser P; auto O = P.parse(v).extract(); ProvObjects::RRMDetails D; if(D.from_json(O)) { return ValidSchedule(D.schedule); } } catch (...) { } return false; } inline bool ValidDeviceRules(const ProvObjects::DeviceRules & DR) { return (ValidRRM(DR.rrm)) && (DR.firmwareUpgrade=="yes" || DR.firmwareUpgrade=="no" || DR.firmwareUpgrade=="inherit") && (DR.rcOnly=="yes" || DR.rcOnly=="no" || DR.rcOnly=="inherit"); } inline bool ValidDeviceRules(const ProvObjects::DeviceRules & DR, RESTAPIHandler &H) { if(ValidDeviceRules(DR)) return true; H.BadRequest(RESTAPI::Errors::InvalidRRM); return false; } inline bool ValidSourceIP([[maybe_unused]] const std::vector & IPs) { return true; } inline bool ValidPeriod(const std::string &P) { return (P=="hourly" || P=="daily" || P=="monthly" || P=="yearly" || P=="quarterly" || P=="lifetime" || P=="custom1" || P=="custom2"|| P=="custom3"|| P=="custom4"); } inline bool ValidContactType(const std::string &contact) { auto C = Poco::toLower(contact); return (C=="subscriber" || C=="user" || C=="installer" || C=="csr" || C=="manager" || C=="businessowner" || C=="technician" || C=="corporate"); } inline bool ValidContactType(const std::string &contact, RESTAPIHandler &H) { auto C = Poco::toLower(contact); if (C=="subscriber" || C=="user" || C=="installer" || C=="csr" || C=="manager" || C=="businessowner" || C=="technician" || C=="corporate") return true; H.BadRequest(RESTAPI::Errors::InvalidContactType); return false; } inline bool ValidLocationType(const std::string &location) { auto C = Poco::toLower(location); return (C=="service" || C=="equipment" || C=="auto" || C=="manual" || C=="special" || C=="unknown" || C=="corporate"); } inline bool ValidLocationType(const std::string &location, RESTAPIHandler &H) { auto C = Poco::toLower(location); if((C=="service" || C=="equipment" || C=="auto" || C=="manual" || C=="special" || C=="unknown" || C=="corporate")) return true; H.BadRequest(RESTAPI::Errors::InvalidLocationType); return false; } template bool ValidDbId(const Types::UUID_t &uuid, DBType & DB, bool AllowEmpty , const RESTAPI::Errors::msg &Error , RESTAPIHandler & H) { if(!AllowEmpty && uuid.empty()) { H.BadRequest(Error); return false; } if(uuid.empty()) return true; if(!DB.Exists("id",uuid)) { H.BadRequest(Error); return false; } return true; } inline bool ValidSubscriberId( const Types::UUID_t & uuid, bool AllowEmpty, RESTAPIHandler &H ) { if(!AllowEmpty && uuid.empty()) { H.BadRequest(RESTAPI::Errors::InvalidSubscriberId); return false; } if(uuid.empty()) return true; SecurityObjects::UserInfo NewSubInfo; if(!SDK::Sec::Subscriber::Get(&H, uuid, NewSubInfo)) { H.BadRequest(RESTAPI::Errors::InvalidSubscriberId); return false; } return true; } inline bool ValidSubscriberId( const Types::UUID_t & uuid, bool AllowEmpty, std::string & email, RESTAPIHandler &H ) { if(!AllowEmpty && uuid.empty()) { H.BadRequest(RESTAPI::Errors::InvalidSubscriberId); return false; } if(uuid.empty()) return true; SecurityObjects::UserInfo NewSubInfo; if(!SDK::Sec::Subscriber::Get(&H, uuid, NewSubInfo)) { H.BadRequest(RESTAPI::Errors::InvalidSubscriberId); return false; } email = NewSubInfo.email; return true; } inline bool ValidSerialNumber(const std::string &serialNumber, bool AllowEmpty, RESTAPIHandler &H) { if(!AllowEmpty && serialNumber.empty()) { H.BadRequest(RESTAPI::Errors::InvalidSerialNumber); return false; } if(!Utils::ValidSerialNumber(serialNumber)) { H.BadRequest(RESTAPI::Errors::InvalidSerialNumber); return false; } return true; } template void ReturnUpdatedObject( DBType & DB, const DBRecordType & R, RESTAPIHandler &H) { if(DB.UpdateRecord("id",R.info.id,R)) { DBRecordType Updated; DB.GetRecord("id",R.info.id,Updated); Poco::JSON::Object Answer; Updated.to_json(Answer); return H.ReturnObject(Answer); } else { H.InternalError(RESTAPI::Errors::RecordNotUpdated); } } template void ReturnCreatedObject( DBType & DB, const DBRecordType & R, RESTAPIHandler &H) { if(DB.CreateRecord(R)) { DBRecordType Updated; DB.GetRecord("id",R.info.id,Updated); Poco::JSON::Object Answer; Updated.to_json(Answer); return H.ReturnObject(Answer); } else { H.InternalError(RESTAPI::Errors::RecordNotCreated); } } template void ReturnFieldList(DBType & DB, RESTAPIHandler &H) { Types::StringVec Fields; DB.GetFieldNames(Fields); Poco::JSON::Object Answer; RESTAPI_utils::field_to_json(Answer,"list",Fields); return H.ReturnObject(Answer); } }