diff --git a/SUBSCRIBERS.md b/SUBSCRIBERS.md new file mode 100644 index 0000000..f7e93c4 --- /dev/null +++ b/SUBSCRIBERS.md @@ -0,0 +1,6 @@ +# Subscribers Architecture + +## Overview +The goal is to provide multiple WISPs with access to a set of subscribers. All subscribers will fall in the default WISP and can be moved to any other WISP later. You can use the source IP to detect which WISP to select. +Entities can be generic entities when created OR WISP entities. + diff --git a/openapi/owprov.yaml b/openapi/owprov.yaml index 882c5c4..13473e8 100644 --- a/openapi/owprov.yaml +++ b/openapi/owprov.yaml @@ -263,6 +263,15 @@ components: - inherit sourceIP: $ref: '#/components/schemas/StringList' + defaultEntity: + type: boolean + default: false + type: + type: string + enum: + - normal + - subscriber + default: normal EntityList: type: object @@ -1165,6 +1174,16 @@ paths: schema: type: boolean required: false + - in: query + decription: entity type + name: type + schema: + type: string + enum: + - subscriber + - normal + default: normal + required: false responses: 200: diff --git a/src/RESTAPI/RESTAPI_db_helpers.h b/src/RESTAPI/RESTAPI_db_helpers.h index cf3d3fd..5f46d40 100644 --- a/src/RESTAPI/RESTAPI_db_helpers.h +++ b/src/RESTAPI/RESTAPI_db_helpers.h @@ -485,4 +485,8 @@ namespace OpenWifi { return Result; } + + inline bool ValidEntityType(const std::string &v) { + return (v=="normal" || v=="subscriber"); + } } diff --git a/src/RESTAPI/RESTAPI_entity_handler.cpp b/src/RESTAPI/RESTAPI_entity_handler.cpp index c4f3c24..4160382 100644 --- a/src/RESTAPI/RESTAPI_entity_handler.cpp +++ b/src/RESTAPI/RESTAPI_entity_handler.cpp @@ -41,6 +41,10 @@ namespace OpenWifi{ return BadRequest(RESTAPI::Errors::CannotDeleteRoot); } + if(Existing.type=="subscriber" && Existing.defaultEntity) { + return BadRequest(RESTAPI::Errors::CannotDeleteSubEntity); + } + if( !Existing.children.empty() || !Existing.devices.empty() || !Existing.venues.empty() || !Existing.locations.empty() || !Existing.contacts.empty() || !Existing.configurations.empty()) { return BadRequest(RESTAPI::Errors::StillInUse); @@ -69,6 +73,14 @@ namespace OpenWifi{ return BadRequest(RESTAPI::Errors::InvalidJSONDocument); } + if(NewEntity.type.empty()) { + NewEntity.type = "normal"; + } + + if(!ValidEntityType(NewEntity.type)) { + return BadRequest(RESTAPI::Errors::InvalidEntityType); + } + if(!ProvObjects::CreateObjectInfo(Obj,UserInfo_.userinfo,NewEntity.info)) { return BadRequest(RESTAPI::Errors::NameMustBeSet); } diff --git a/src/RESTAPI/RESTAPI_entity_list_handler.cpp b/src/RESTAPI/RESTAPI_entity_list_handler.cpp index f5caeb4..7c3ec20 100644 --- a/src/RESTAPI/RESTAPI_entity_list_handler.cpp +++ b/src/RESTAPI/RESTAPI_entity_list_handler.cpp @@ -14,11 +14,16 @@ namespace OpenWifi{ void RESTAPI_entity_list_handler::DoGet() { + auto type = GetParameter("type","normal"); + if(!ValidEntityType(type)) { + return BadRequest(RESTAPI::Errors::InvalidEntityType); + } + if(!QB_.Select.empty()) { return ReturnRecordList("entities",DB_,*this ); } else if(QB_.CountOnly) { - auto C = DB_.Count(); + auto C = DB_.Count(fmt::format(" where type='{}'", type)); return ReturnCountOnly(C); } else if (GetBoolParameter("getTree",false)) { Poco::JSON::Object FullTree; @@ -26,7 +31,8 @@ namespace OpenWifi{ return ReturnObject(FullTree); } else { EntityDB::RecordVec Entities; - DB_.GetRecords(QB_.Offset, QB_.Limit,Entities); + + DB_.GetRecords(QB_.Offset, QB_.Limit,Entities,fmt::format(" where type='{}'", type)); return MakeJSONObjectArray("entities", Entities, *this); } } diff --git a/src/RESTObjects/RESTAPI_ProvObjects.cpp b/src/RESTObjects/RESTAPI_ProvObjects.cpp index b513302..b46f255 100644 --- a/src/RESTObjects/RESTAPI_ProvObjects.cpp +++ b/src/RESTObjects/RESTAPI_ProvObjects.cpp @@ -98,6 +98,8 @@ namespace OpenWifi::ProvObjects { field_to_json( Obj,"managementRoles", managementRoles); field_to_json( Obj,"maps", maps); field_to_json( Obj,"configurations", configurations); + field_to_json( Obj,"type", type); + field_to_json( Obj,"defaultEntity", defaultEntity); } bool Entity::from_json(const Poco::JSON::Object::Ptr &Obj) { @@ -118,6 +120,8 @@ namespace OpenWifi::ProvObjects { field_from_json( Obj,"managementRoles", managementRoles); field_from_json( Obj,"maps", maps); field_from_json( Obj,"configurations", configurations); + field_from_json( Obj,"type", type); + field_from_json( Obj,"defaultEntity", defaultEntity); return true; } catch(...) { diff --git a/src/RESTObjects/RESTAPI_ProvObjects.h b/src/RESTObjects/RESTAPI_ProvObjects.h index 8e75500..347e1f2 100644 --- a/src/RESTObjects/RESTAPI_ProvObjects.h +++ b/src/RESTObjects/RESTAPI_ProvObjects.h @@ -79,6 +79,8 @@ namespace OpenWifi::ProvObjects { Types::UUIDvec_t managementRoles; Types::UUIDvec_t maps; Types::UUIDvec_t configurations; + std::string type; + bool defaultEntity=false; void to_json(Poco::JSON::Object &Obj) const; bool from_json(const Poco::JSON::Object::Ptr &Obj); diff --git a/src/StorageService.cpp b/src/StorageService.cpp index 72b9ae9..658a225 100644 --- a/src/StorageService.cpp +++ b/src/StorageService.cpp @@ -77,6 +77,8 @@ namespace OpenWifi { ConsistencyCheck(); + CreateDefaultSubscriberEntity(); + TimerCallback_ = std::make_unique>(*this,&Storage::onTimer); Timer_.setStartInterval( 20 * 1000); // first run in 20 seconds Timer_.setPeriodicInterval(1 * 60 * 60 * 1000); // 1 hours @@ -222,6 +224,32 @@ namespace OpenWifi { } + void Storage::CreateDefaultSubscriberEntity() { + + std::vector Entities; + if(EntityDB().GetRecords(0,1,Entities, " where type='subscriber' and defaultEntity=true ")) { + DefaultSubscriberEntity_ = Entities[0].info.id; + } else { + ProvObjects::Entity DefEntity; + + DefEntity.info.name = "Default Subscriber Entity"; + DefaultSubscriberEntity_ = DefEntity.info.id = MicroService::CreateUUID(); + DefEntity.type = "subscriber"; + DefEntity.defaultEntity = true; + DefEntity.info.created = DefEntity.info.modified = OpenWifi::Now(); + DefEntity.rrm = "inherit"; + EntityDB().CreateRecord(DefEntity); + } + + // To be backwards compatible, we need to assign all the subscribers that do not have an entity to + // the default entity. + // We also need to make sure that all entities have some type set. + std::vector Script{ + "update entities set type='normal' where type='' ", + fmt::format("update inventory set entity='{}' where entity='' and subscriber!='' ", DefaultSubscriberEntity_) + }; + EntityDB().RunScript(Script); + } } // namespace \ No newline at end of file diff --git a/src/StorageService.h b/src/StorageService.h index 04038e2..431ae5b 100644 --- a/src/StorageService.h +++ b/src/StorageService.h @@ -62,6 +62,8 @@ namespace OpenWifi { void onTimer(Poco::Timer & timer); + inline const std::string & DefaultSubscriberEntity() { return DefaultSubscriberEntity_; } + private: std::unique_ptr EntityDB_; std::unique_ptr PolicyDB_; @@ -76,6 +78,7 @@ namespace OpenWifi { std::unique_ptr MapDB_; std::unique_ptr SignupDB_; std::unique_ptr VariablesDB_; + std::string DefaultSubscriberEntity_; typedef std::function exist_func; @@ -86,6 +89,7 @@ namespace OpenWifi { std::unique_ptr> TimerCallback_; void ConsistencyCheck(); + void CreateDefaultSubscriberEntity(); }; diff --git a/src/framework/orm.h b/src/framework/orm.h index 48c95f7..2914a7e 100644 --- a/src/framework/orm.h +++ b/src/framework/orm.h @@ -529,6 +529,21 @@ namespace ORM { return false; } + bool RunStatement(const std::string &St) { + try { + Poco::Data::Session Session = Pool_.get(); + Poco::Data::Statement Command(Session); + + Command << St ; + Command.execute(); + + return true; + } catch (const Poco::Exception &E) { + Logger_.log(E); + } + return false; + } + template bool ReplaceRecord(field_name_t FieldName, const T & Value, RecordType & R) { try { if(Exists(FieldName, Value)) { diff --git a/src/framework/ow_constants.h b/src/framework/ow_constants.h index 14ebc11..cc7ba8e 100644 --- a/src/framework/ow_constants.h +++ b/src/framework/ow_constants.h @@ -82,6 +82,8 @@ namespace OpenWifi::RESTAPI::Errors { static const std::string UserAlreadyExists{"Username already exists."}; static const std::string NotImplemented{"Function not implemented."}; static const std::string VariableMustExist{"Specified variable does not exist."}; + static const std::string InvalidEntityType{"Invalid entity type."}; + static const std::string CannotDeleteSubEntity{"Cannot delete the default subscriber entity."}; } namespace OpenWifi::RESTAPI::Protocol { diff --git a/src/storage/storage_entity.cpp b/src/storage/storage_entity.cpp index 8b48729..d2ca0d7 100644 --- a/src/storage/storage_entity.cpp +++ b/src/storage/storage_entity.cpp @@ -39,7 +39,9 @@ namespace OpenWifi { ORM::Field{"managementPolicies",ORM::FieldType::FT_TEXT}, ORM::Field{"managementRoles",ORM::FieldType::FT_TEXT}, ORM::Field{"maps",ORM::FieldType::FT_TEXT}, - ORM::Field{"configurations",ORM::FieldType::FT_TEXT} + ORM::Field{"configurations",ORM::FieldType::FT_TEXT}, + ORM::Field{"type",ORM::FieldType::FT_TEXT}, + ORM::Field{"defaultEntity",ORM::FieldType::FT_TEXT} }; static ORM::IndexVec EntityDB_Indexes{ @@ -62,6 +64,8 @@ namespace OpenWifi { "alter table " + TableName_ + " add column managementPolicies text", "alter table " + TableName_ + " add column maps text", "alter table " + TableName_ + " add column configurations text", + "alter table " + TableName_ + " add column type text", + "alter table " + TableName_ + " add column defaultEntity boolean", "alter table " + TableName_ + " add column managementRoles text" }; @@ -230,6 +234,8 @@ template<> void ORM::DB< OpenWifi::EntityDBRecordType, OpenWifi::ProvObjects: Out.managementRoles = OpenWifi::RESTAPI_utils::to_object_array(In.get<19>()); Out.maps = OpenWifi::RESTAPI_utils::to_object_array(In.get<20>()); Out.configurations = OpenWifi::RESTAPI_utils::to_object_array(In.get<21>()); + Out.type = In.get<22>(); + Out.defaultEntity = In.get<23>(); } template<> void ORM::DB< OpenWifi::EntityDBRecordType, OpenWifi::ProvObjects::Entity>::Convert(const OpenWifi::ProvObjects::Entity &In, OpenWifi::EntityDBRecordType &Out) { @@ -255,4 +261,6 @@ template<> void ORM::DB< OpenWifi::EntityDBRecordType, OpenWifi::ProvObjects: Out.set<19>(OpenWifi::RESTAPI_utils::to_string(In.managementRoles)); Out.set<20>(OpenWifi::RESTAPI_utils::to_string(In.maps)); Out.set<21>(OpenWifi::RESTAPI_utils::to_string(In.configurations)); + Out.set<22>(In.type); + Out.set<23>(In.defaultEntity); } diff --git a/src/storage/storage_entity.h b/src/storage/storage_entity.h index 9636a9b..8ad7204 100644 --- a/src/storage/storage_entity.h +++ b/src/storage/storage_entity.h @@ -35,7 +35,9 @@ namespace OpenWifi { std::string, std::string, std::string, - std::string + std::string, + std::string, + bool > EntityDBRecordType; class EntityDB : public ORM::DB {