From 1328d943c5016dd1662a4e46d4a408bca010cffc Mon Sep 17 00:00:00 2001 From: Aleksi Lindeman <0xdec05eba@gmail.com> Date: Sun, 11 Mar 2018 00:12:37 +0100 Subject: Add operation to allow users to be added to group WARNING! Lazy implementation everywhere, does not handle out-of-order packets --- src/DataView.cpp | 10 +++ src/Database.cpp | 192 +++++++++++++++++++++++++++++++++++++++++------- src/DatabaseStorage.cpp | 43 ++++++++++- src/Group.cpp | 17 ++++- src/Permission.cpp | 19 +++++ src/User.cpp | 26 +++++++ 6 files changed, 274 insertions(+), 33 deletions(-) create mode 100644 src/DataView.cpp create mode 100644 src/Permission.cpp create mode 100644 src/User.cpp (limited to 'src') diff --git a/src/DataView.cpp b/src/DataView.cpp new file mode 100644 index 0000000..63c4f07 --- /dev/null +++ b/src/DataView.cpp @@ -0,0 +1,10 @@ +#include "../include/DataView.hpp" +#include + +namespace odhtdb +{ + bool DataView::operator == (const DataView &other) const + { + return size == other.size && memcmp(data, other.data, size) == 0; + } +} diff --git a/src/Database.cpp b/src/Database.cpp index d01ff7d..fc1a69b 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -5,6 +5,7 @@ #include "../include/Encryption.hpp" #include "../include/DhtKey.hpp" #include "../include/bin2hex.hpp" +#include #include #include #include @@ -236,9 +237,11 @@ namespace odhtdb unique_ptr Database::create(const std::string &ownerName, const std::string &nodeName) { - LocalUser *nodeAdminUser = LocalUser::create(Signature::KeyPair(), ownerName); - auto adminGroup = new Group("administrator"); - adminGroup->addUser(nodeAdminUser); + // TODO: Should this be declared static? is there any difference in behavior/performance? + boost::uuids::random_generator uuidGen; + auto adminGroupId = uuidGen(); + auto adminGroup = new Group("administrator", adminGroupId.data, ADMIN_PERMISSION); + LocalUser *nodeAdminUser = LocalUser::create(Signature::KeyPair(), ownerName, adminGroup); // Header sibs::SafeSerializer serializer; @@ -247,6 +250,7 @@ namespace odhtdb u64 timestampMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; serializer.add(timestampMicroseconds); serializer.add((u8*)nodeAdminUser->getPublicKey().getData(), PUBLIC_KEY_NUM_BYTES); + serializer.add(adminGroupId.data, adminGroupId.size()); // Encrypted body sibs::SafeSerializer encryptedSerializer; @@ -276,22 +280,72 @@ namespace odhtdb } } - void Database::add(const unique_ptr &nodeInfo, DataView dataToAdd) + void Database::addData(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, DataView dataToAdd) { sibs::SafeSerializer serializer; serializer.add(DATABASE_ADD_PACKET_STRUCTURE_VERSION); // TODO: Append fractions to get real microseconds time u64 timestampMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; serializer.add(timestampMicroseconds); + serializer.add(DatabaseOperation::ADD_DATA); - DataView encryptionKey(*nodeInfo->getNodeEncryptionKey(), KEY_BYTE_SIZE); + DataView encryptionKey(*nodeInfo.getNodeEncryptionKey(), KEY_BYTE_SIZE); Encryption encryptedBody(dataToAdd, DataView(), encryptionKey); DataView requestData = combine(serializer, encryptedBody); - string signedRequestData = nodeInfo->getNodeAdminUser()->getPrivateKey().sign(requestData); + string signedRequestData = userToPerformActionWith->getPrivateKey().sign(requestData); free(requestData.data); - DataView stagedAddObject = combine(nodeInfo->getNodeAdminUser()->getPublicKey(), signedRequestData); + DataView stagedAddObject = combine(userToPerformActionWith->getPublicKey(), signedRequestData); // TODO: Add add object to database storage here for local user - stagedAddObjects.emplace_back(make_unique(stagedAddObject, nodeInfo->getRequestHash())); + stagedAddObjects.emplace_back(make_unique(stagedAddObject, nodeInfo.getRequestHash())); + } + + Group* getGroupWithRightsToAddUserToGroup(const vector &groups, Group *groupToAddUserTo) + { + for(auto group : groups) + { + const auto &groupPermission = group->getPermission(); + if(groupPermission.getFlag(PermissionType::ADD_USER_LOWER_LEVEL) && groupPermission.getPermissionLevel() < groupToAddUserTo->getPermission().getPermissionLevel()) + { + return group; + } + else if(groupPermission.getFlag(PermissionType::ADD_USER_SAME_LEVEL) && groupPermission.getPermissionLevel() == groupToAddUserTo->getPermission().getPermissionLevel()) + { + return group; + } + } + return nullptr; + } + + void Database::addUserToGroup(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const std::string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo) + { + auto groupWithAddUserRights = getGroupWithRightsToAddUserToGroup(userToPerformActionWith->getGroups(), groupToAddUserTo); + if(!groupWithAddUserRights) + { + string errMsg = "The user "; + errMsg += userToPerformActionWith->getName(); + errMsg += " does not belong to any group that is allowed to add an user to the group "; + errMsg += groupToAddUserTo->getName(); + throw PermissionDeniedException(errMsg); + } + + sibs::SafeSerializer serializer; + serializer.add(DATABASE_ADD_PACKET_STRUCTURE_VERSION); + // TODO: Append fractions to get real microseconds time + u64 timestampMicroseconds = ((u64)getSyncedTimestampUtc().seconds) * 1000000ull; + serializer.add(timestampMicroseconds); + serializer.add(DatabaseOperation::ADD_USER); + + assert(userToAddName.size() <= 255); + serializer.add((u8)userToAddName.size()); + serializer.add((u8*)userToAddName.data(), userToAddName.size()); + serializer.add((u8*)userToAddPublicKey.getData(), PUBLIC_KEY_NUM_BYTES); + serializer.add((uint8_t*)groupToAddUserTo->getId().data, groupToAddUserTo->getId().size); + + DataView requestData { serializer.getBuffer().data(), serializer.getBuffer().size() }; + string signedRequestData = userToPerformActionWith->getPrivateKey().sign(requestData); + DataView stagedAddObject = combine(userToPerformActionWith->getPublicKey(), signedRequestData); + // TODO: Add add object to database storage here for local user + stagedAddObjects.emplace_back(make_unique(stagedAddObject, nodeInfo.getRequestHash())); } void Database::commit() @@ -389,12 +443,15 @@ namespace odhtdb deserializer.extract((u8*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); Signature::PublicKey userPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + uint8_t adminGroupId[16]; + deserializer.extract(adminGroupId, 16); + if(deserializer.getSize() < NONCE_BYTE_SIZE) throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); - auto adminGroup = new Group("administrator"); - auto creatorUser = RemoteUser::create(userPublicKey, "ENCRYPTED USER NAME"); // Username is encrypted, we dont know it... - adminGroup->addUser(creatorUser); + auto adminGroup = new Group("administrator", adminGroupId, ADMIN_PERMISSION); + // TODO: Username is encrypted, we dont know it... unless we have encryption key, in which case we should modify the user name and set it + auto creatorUser = RemoteUser::create(userPublicKey, "ENCRYPTED USER NAME", adminGroup); databaseStorage.createStorage(hash, adminGroup, creationDate, value->data.data(), value->data.size()); u8 nonce[NONCE_BYTE_SIZE]; @@ -415,16 +472,26 @@ namespace odhtdb return { creationDate, adminGroup, move(name) }; } + + bool isUserAllowedToAddData(const User *user) + { + for(Group *group : user->getGroups()) + { + if(group->getPermission().getFlag(PermissionType::ADD_DATA)) + return true; + } + return false; + } - DatabaseAddRequest Database::deserializeAddRequest(const std::shared_ptr &value, const Hash &hash, const shared_ptr encryptionKey) + void Database::deserializeAddRequest(const std::shared_ptr &value, const Hash &hash, const shared_ptr encryptionKey) { sibs::SafeDeserializer deserializer(value->data.data(), value->data.size()); char creatorPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; deserializer.extract((u8*)creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); - Signature::PublicKey userPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + Signature::PublicKey creatorPublicKey(creatorPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); DataView signedData((void*)deserializer.getBuffer(), deserializer.getSize()); - string unsignedData = userPublicKey.unsign(signedData); + string unsignedData = creatorPublicKey.unsign(signedData); sibs::SafeDeserializer deserializerUnsigned((u8*)unsignedData.data(), unsignedData.size()); u16 packetStructureVersion = deserializerUnsigned.extract(); @@ -443,29 +510,98 @@ namespace odhtdb if(creationDate > timestampMicroseconds) throw sibs::DeserializeException("Packet is from the future"); - if(deserializerUnsigned.getSize() < NONCE_BYTE_SIZE) - throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); + DatabaseOperation operation = deserializerUnsigned.extract(); - const Hash *node = databaseStorage.getNodeByUserPublicKey(userPublicKey); + const Hash *node = databaseStorage.getNodeByUserPublicKey(creatorPublicKey); if(!node) { // The user (public key) could belong to a node but we might not have retrieved the node info yet since data may // not be retrieved in order. // Data in quarantine is processed when 'create' packet is received or removed after 60 seconds - databaseStorage.addToQuarantine(userPublicKey, creationDate, value->data.data(), value->data.size()); + databaseStorage.addToQuarantine(creatorPublicKey, creationDate, value->data.data(), value->data.size()); throw RequestQuarantineException(); } - auto creatorUser = databaseStorage.getUserByPublicKey(userPublicKey); - // TODO: Verify there isn't already data with same timestamp for this node. Same for quarantine + auto creatorUser = databaseStorage.getUserByPublicKey(creatorPublicKey); + // TODO: Verify there isn't already data with same timestamp for this node. Same for quarantine. + // TODO: We might receive 'add' data packet before 'create'. If that happens, we should put it in quarantine and process it later. databaseStorage.appendStorage(*node, creatorUser, creationDate, value->data.data(), value->data.size()); - u8 nonce[NONCE_BYTE_SIZE]; - deserializerUnsigned.extract(nonce, NONCE_BYTE_SIZE); - DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); - Decryption decryptedBody(dataToDecrypt, DataView(nonce, NONCE_BYTE_SIZE), DataView(*encryptionKey, KEY_BYTE_SIZE)); - - return { creationDate, creatorUser, move(decryptedBody) }; + if(operation == DatabaseOperation::ADD_DATA) + { + if(deserializerUnsigned.getSize() < NONCE_BYTE_SIZE) + throw sibs::DeserializeException("Unsigned encrypted body is too small (unable to extract nonce)"); + + u8 nonce[NONCE_BYTE_SIZE]; + deserializerUnsigned.extract(nonce, NONCE_BYTE_SIZE); + DataView dataToDecrypt((void*)deserializerUnsigned.getBuffer(), deserializerUnsigned.getSize()); + Decryption decryptedBody(dataToDecrypt, DataView(nonce, NONCE_BYTE_SIZE), DataView(*encryptionKey, KEY_BYTE_SIZE)); + + if(!isUserAllowedToAddData(creatorUser)) + { + // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, + // or we haven't received the packet that modifies group with the permission to perform the operation. + // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, + // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. + // How to handle this? + string errMsg = "User "; + errMsg += creatorUser->getName(); + errMsg += " is not allowed to perform the operation: "; + errMsg += to_string((u8)operation); + throw PermissionDeniedException(errMsg); + } + + printf("Got add object, timestamp: %zu, data: %.*s\n", creationDate, decryptedBody.getDecryptedText().size, decryptedBody.getDecryptedText().data); + } + else if(operation == DatabaseOperation::ADD_USER) + { + + u8 nameLength = deserializerUnsigned.extract(); + string name; + name.resize(nameLength); + deserializerUnsigned.extract((u8*)&name[0], nameLength); + + char userToAddPublicKeyRaw[PUBLIC_KEY_NUM_BYTES]; + deserializerUnsigned.extract((u8*)userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + Signature::PublicKey userToAddPublicKey(userToAddPublicKeyRaw, PUBLIC_KEY_NUM_BYTES); + + uint8_t groupId[16]; + deserializerUnsigned.extract(groupId, 16); + + auto group = databaseStorage.getGroupById(groupId); + if(group) + { + auto user = RemoteUser::create(userToAddPublicKey, name, group); + databaseStorage.addUser(user, hash); + + auto creatorUserGroupWithRights = getGroupWithRightsToAddUserToGroup(creatorUser->getGroups(), group); + if(!creatorUserGroupWithRights) + { + // TODO: User might have permission to perform operation, but we haven't got the packet that adds user to the group with the permission, + // or we haven't received the packet that modifies group with the permission to perform the operation. + // This also means that an user can be in a group that has permission to perform the operation and then later be removed from it, + // and remote peers would accept our request to perform operation if they haven't received the operation that removes the user from the group. + // How to handle this? + string errMsg = "User "; + errMsg += creatorUser->getName(); + errMsg += " is not allowed to perform the operation: "; + errMsg += to_string((u8)operation); + throw PermissionDeniedException(errMsg); + } + + printf("Got add user object, timestamp: %zu, user added: %.*s\n", creationDate, nameLength, name.c_str()); + } + else + { + throw sibs::DeserializeException("TODO: Add to quarantine? You can receive ADD_USER packet before you receive ADD_GROUP"); + } + } + else + { + string errMsg = "Got unexpected operation: "; + errMsg += to_string((u8)operation); + throw sibs::DeserializeException(errMsg); + } } bool Database::listenCreateData(std::shared_ptr value, const Hash &hash, const shared_ptr encryptionKey) @@ -490,8 +626,8 @@ namespace odhtdb printf("Got add data\n"); try { - DatabaseAddRequest addObject = deserializeAddRequest(value, hash, encryptionKey); - printf("Got add object, timestamp: %zu\n", addObject.timestamp); + deserializeAddRequest(value, hash, encryptionKey); + //printf("Got add object, timestamp: %zu\n", addObject.timestamp); } catch (RequestQuarantineException &e) { diff --git a/src/DatabaseStorage.cpp b/src/DatabaseStorage.cpp index 638eac0..7c86d18 100644 --- a/src/DatabaseStorage.cpp +++ b/src/DatabaseStorage.cpp @@ -1,6 +1,6 @@ #include "../include/DatabaseStorage.hpp" -#include "../include/Group.hpp" #include "../include/User.hpp" +#include "../include/Group.hpp" #include #include @@ -8,6 +8,8 @@ using namespace std; namespace odhtdb { + const u64 QUARANTINE_STORAGE_TIME_MICROSECONDS = 60 * 1.0e6; + DatabaseStorageObject::DatabaseStorageObject(DataView &_data, u64 _timestamp, const Signature::PublicKey &_creatorPublicKey) : data(_data), createdTimestamp(_timestamp), creatorPublicKey(_creatorPublicKey) { @@ -37,11 +39,11 @@ namespace odhtdb databaseStorageObjectList->data = DataView(new u8[dataSize], dataSize); memcpy(databaseStorageObjectList->data.data, data, dataSize); storageMap[hash] = databaseStorageObjectList; + groupByIdMap[creatorGroup->getId()] = creatorGroup; for(auto user : creatorGroup->getUsers()) { - userPublicKeyNodeMap[user->getPublicKey()] = hash; - publicKeyUserMap[user->getPublicKey()] = user; + addUser((User*)user, hash); } } @@ -69,6 +71,12 @@ namespace odhtdb quarantineStorageMap[creatorPublicKey].emplace_back(databaseQuarantineStorageObject); } + void DatabaseStorage::addUser(User *user, const Hash &hash) + { + userPublicKeyNodeMap[user->getPublicKey()] = hash; + publicKeyUserMap[user->getPublicKey()] = user; + } + const DatabaseStorageObjectList* DatabaseStorage::getStorage(const Hash &hash) const { auto it = storageMap.find(hash); @@ -93,4 +101,33 @@ namespace odhtdb return it->second; return nullptr; } + + Group* DatabaseStorage::getGroupById(uint8_t groupId[16]) + { + auto it = groupByIdMap.find(DataView(groupId, 16)); + if(it != groupByIdMap.end()) + return it->second; + return nullptr; + } + + void DatabaseStorage::update() + { + auto time = chrono::high_resolution_clock::now().time_since_epoch(); + auto timeMicroseconds = chrono::duration_cast(time).count(); + for(auto mapIt = quarantineStorageMap.begin(); mapIt != quarantineStorageMap.end(); ) + { + for(auto vecIt = mapIt->second.begin(); vecIt != mapIt->second.end(); ) + { + if(timeMicroseconds - (*vecIt)->storedTimestamp > QUARANTINE_STORAGE_TIME_MICROSECONDS) + vecIt = mapIt->second.erase(vecIt); + else + ++vecIt; + } + + if(mapIt->second.empty()) + mapIt = quarantineStorageMap.erase(mapIt); + else + ++mapIt; + } + } } diff --git a/src/Group.cpp b/src/Group.cpp index 93a0688..12b50ed 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -1,15 +1,18 @@ #include "../include/Group.hpp" #include "../include/User.hpp" +#include using namespace std; namespace odhtdb { - Group::Group(const string &_name) : - name(_name) + Group::Group(const string &_name, uint8_t _id[16], const Permission &_permission) : + name(_name), + permission(_permission) { if(name.size() > 255) throw GroupNameTooLongException(name); + memcpy(id, _id, 16); } Group::~Group() @@ -26,6 +29,16 @@ namespace odhtdb { return name; } + + DataView Group::getId() const + { + return { (void*)id, 16 }; + } + + const Permission& Group::getPermission() const + { + return permission; + } const vector& Group::getUsers() const { diff --git a/src/Permission.cpp b/src/Permission.cpp new file mode 100644 index 0000000..de0a747 --- /dev/null +++ b/src/Permission.cpp @@ -0,0 +1,19 @@ +#include "../include/Permission.hpp" + +namespace odhtdb +{ + Permission::Permission(u8 _permissionLevel, std::initializer_list permissions) : + permissionLevel(_permissionLevel) + { + permissionFlags = 0; + for(auto permission : permissions) + { + permissionFlags |= (u32)permission; + } + } + + bool Permission::getFlag(PermissionType permissionType) const + { + return (permissionFlags & (u32)permissionType) != 0; + } +} diff --git a/src/User.cpp b/src/User.cpp new file mode 100644 index 0000000..e2017ff --- /dev/null +++ b/src/User.cpp @@ -0,0 +1,26 @@ +#include "../include/User.hpp" +#include "../include/Group.hpp" + +namespace odhtdb +{ + User::User(const std::string &_name, Group *group) : name(_name) + { + if(name.size() > 255) + throw UserNameTooLongException(name); + + if(group) + { + groups.emplace_back(group); + group->addUser(this); + } + } + + void User::addToGroup(Group *group) + { + if(group) + { + groups.emplace_back(group); + group->addUser(this); + } + } +} -- cgit v1.2.3