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 --- Scheme.md | 16 +++- include/DataView.hpp | 14 ++++ include/Database.hpp | 14 +++- include/DatabaseNode.hpp | 31 +++++++ include/DatabaseStorage.hpp | 10 +++ include/Group.hpp | 16 +++- include/LocalUser.hpp | 6 +- include/Permission.hpp | 46 +++++++++++ include/RemoteUser.hpp | 6 +- include/User.hpp | 13 +-- project.conf | 1 + src/DataView.cpp | 10 +++ src/Database.cpp | 192 +++++++++++++++++++++++++++++++++++++------- src/DatabaseStorage.cpp | 43 +++++++++- src/Group.cpp | 17 +++- src/Permission.cpp | 19 +++++ src/User.cpp | 26 ++++++ tests/main.cpp | 12 ++- 18 files changed, 438 insertions(+), 54 deletions(-) create mode 100644 include/DatabaseNode.hpp create mode 100644 include/Permission.hpp create mode 100644 src/DataView.cpp create mode 100644 src/Permission.cpp create mode 100644 src/User.cpp diff --git a/Scheme.md b/Scheme.md index 61fdce5..f573f73 100644 --- a/Scheme.md +++ b/Scheme.md @@ -4,15 +4,29 @@ Packet packet structure version timestamp creator public key + admin group id (uuid, 16 bytes) Body (Encrypted) creator name name -# Add +# Add data Packet creator public key Content (Signed with creator private key, verify with creator public key) Header packet structure version timestamp + operation type Body (Encrypted with node encryption key) data +# Add user to group +Packet + creator public key + Content (Signed with creator private key, verify with creator public key) + Header + packet structure version + timestamp + operation type + Body + name (of the user to add to group) + public key (of user to add to group) + group id (the group which the user should be added to) diff --git a/include/DataView.hpp b/include/DataView.hpp index c020f91..0ecf9fb 100644 --- a/include/DataView.hpp +++ b/include/DataView.hpp @@ -1,6 +1,8 @@ #pragma once #include "types.hpp" +#include "Hash.hpp" +#include namespace odhtdb { @@ -9,8 +11,20 @@ namespace odhtdb public: DataView() : data(nullptr), size(0) {} DataView(void *_data, usize _size) : data(_data), size(_size) {} + bool operator == (const DataView &other) const; void *data; usize size; }; + + struct DataViewHasher + { + size_t operator()(const DataView &dataView) const + { + return fnvHash((const unsigned char*)dataView.data, dataView.size); + } + }; + + template + using DataViewMap = std::unordered_map; } diff --git a/include/Database.hpp b/include/Database.hpp index 1024fe0..64a381c 100644 --- a/include/Database.hpp +++ b/include/Database.hpp @@ -8,6 +8,8 @@ #include "utils.hpp" #include "StagedObject.hpp" #include "Signature.hpp" +#include "Permission.hpp" +#include "DatabaseNode.hpp" #include #include #include @@ -44,6 +46,12 @@ namespace odhtdb DatabaseAddException(const std::string &errMsg) : std::runtime_error(errMsg) {} }; + enum class DatabaseOperation : u8 + { + ADD_DATA, + ADD_USER + }; + struct DatabaseCreateRequest { DISABLE_COPY(DatabaseCreateRequest) @@ -122,7 +130,9 @@ namespace odhtdb // Throws DatabaseCreateException on failure. std::unique_ptr create(const std::string &ownerName, const std::string &nodeName); // Throws DatabaseAddException on failure - void add(const std::unique_ptr &nodeInfo, DataView dataToAdd); + void addData(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, DataView dataToAdd); + // Throws PermissionDeniedException if user @userToPerformActionWith is not allowed to add user @userToAdd to group @groupToAddUserTo + void addUserToGroup(const DatabaseNode &nodeInfo, LocalUser *userToPerformActionWith, const std::string &userToAddName, const Signature::PublicKey &userToAddPublicKey, Group *groupToAddUserTo); void commit(); private: // Throws CommitCreateException on failure @@ -131,7 +141,7 @@ namespace odhtdb void commitStagedAddObject(const std::unique_ptr &stagedObject); ntp::NtpTimestamp getSyncedTimestampUtc() const; DatabaseCreateRequest deserializeCreateRequest(const std::shared_ptr &value, const Hash &hash, const std::shared_ptr encryptionKey); - DatabaseAddRequest deserializeAddRequest(const std::shared_ptr &value, const Hash &hash, const std::shared_ptr encryptionKey); + void deserializeAddRequest(const std::shared_ptr &value, const Hash &hash, const std::shared_ptr encryptionKey); bool listenCreateData(std::shared_ptr value, const Hash &hash, const std::shared_ptr encryptionKey); bool listenAddData(std::shared_ptr value, const Hash &hash, const std::shared_ptr encryptionKey); private: diff --git a/include/DatabaseNode.hpp b/include/DatabaseNode.hpp new file mode 100644 index 0000000..3ca4be3 --- /dev/null +++ b/include/DatabaseNode.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "Hash.hpp" +#include + +namespace odhtdb +{ + class DatabaseNode + { + public: + DatabaseNode(const std::shared_ptr &_encryptionKey, const std::shared_ptr &_nodeHash) : + encryptionKey(_encryptionKey), + nodeHash(_nodeHash) + { + + } + + const std::shared_ptr getNodeEncryptionKey() const + { + return encryptionKey; + } + + const std::shared_ptr getRequestHash() const + { + return nodeHash; + } + private: + std::shared_ptr encryptionKey; + std::shared_ptr nodeHash; + }; +} diff --git a/include/DatabaseStorage.hpp b/include/DatabaseStorage.hpp index fd29050..ee4d2ad 100644 --- a/include/DatabaseStorage.hpp +++ b/include/DatabaseStorage.hpp @@ -1,5 +1,6 @@ #pragma once +#include "types.hpp" #include "Hash.hpp" #include "DataView.hpp" #include "Signature.hpp" @@ -65,6 +66,8 @@ namespace odhtdb void addToQuarantine(const Signature::PublicKey &creatorPublicKey, u64 timestamp, const u8 *data, usize dataSize); + void addUser(User *user, const Hash &hash); + // Returns nullptr if no storage with provided hash exists const DatabaseStorageObjectList* getStorage(const Hash &hash) const; @@ -73,10 +76,17 @@ namespace odhtdb // Returns nullptr if no user with public key exists const User* getUserByPublicKey(const Signature::PublicKey &userPublicKey) const; + + // Returns nullptr if a group with id @groupId doesn't exist + Group* getGroupById(uint8_t groupId[16]); + + // Update storage state (remove quarantine objects if they are too old, etc) + void update(); private: DatabaseStorageMap storageMap; DatabaseStorageQuarantineMap quarantineStorageMap; Signature::MapPublicKey userPublicKeyNodeMap; Signature::MapPublicKey publicKeyUserMap; + DataViewMap groupByIdMap; }; } diff --git a/include/Group.hpp b/include/Group.hpp index a8dcf83..315961d 100644 --- a/include/Group.hpp +++ b/include/Group.hpp @@ -1,5 +1,8 @@ #pragma once +#include "types.hpp" +#include "DataView.hpp" +#include "Permission.hpp" #include #include #include @@ -17,19 +20,26 @@ namespace odhtdb } }; + + class Group { + friend class User; public: - Group(const std::string &name); + Group(const std::string &name, uint8_t id[16], const Permission &permission); ~Group(); - void addUser(const User *user); - const std::string& getName() const; + DataView getId() const; + const Permission& getPermission() const; const std::vector& getUsers() const; + private: + void addUser(const User *user); private: std::string name; + uint8_t id[16]; + Permission permission; std::vector users; }; } diff --git a/include/LocalUser.hpp b/include/LocalUser.hpp index 04f483d..d60cb38 100644 --- a/include/LocalUser.hpp +++ b/include/LocalUser.hpp @@ -7,9 +7,9 @@ namespace odhtdb class LocalUser : public User { public: - static LocalUser* create(const Signature::KeyPair &keyPair, const std::string &name) + static LocalUser* create(const Signature::KeyPair &keyPair, const std::string &name, Group *group) { - return new LocalUser(keyPair, name); + return new LocalUser(keyPair, name, group); } const Signature::PublicKey& getPublicKey() const override @@ -22,7 +22,7 @@ namespace odhtdb return keyPair.getPrivateKey(); } private: - LocalUser(const Signature::KeyPair &_keyPair, const std::string &name) : User(name), keyPair(_keyPair) {} + LocalUser(const Signature::KeyPair &_keyPair, const std::string &name, Group *group) : User(name, group), keyPair(_keyPair) {} private: Signature::KeyPair keyPair; }; diff --git a/include/Permission.hpp b/include/Permission.hpp new file mode 100644 index 0000000..1ae2642 --- /dev/null +++ b/include/Permission.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "types.hpp" +#include +#include + +namespace odhtdb +{ + class PermissionDeniedException : public std::runtime_error + { + public: + PermissionDeniedException(const std::string &errMsg) : std::runtime_error(errMsg) {} + }; + + enum class PermissionType : u32 + { + ADD_DATA = (1 << 0), + ADD_USER_SAME_LEVEL = (1 << 1), + ADD_USER_LOWER_LEVEL = (1 << 2), + ADD_GROUP = (1 << 3), + REMOVE_GROUP = (1 << 4) + }; + + const PermissionType ALL_PERMISSION_TYPES = (PermissionType)0xFFFFFFFF; + + const u8 PERMISSION_LEVEL_ADMIN = 0; + const u8 PERMISSION_LEVEL_MODERATOR = 1; + const u8 PERMISSION_LEVEL_REGULAR_USER = 2; + + class Permission + { + public: + // @permissionLevel is hierarchical access right. A group can only modify a group that has higher @permissionLevel value + Permission(u8 permissionLevel, std::initializer_list permissions); + + u8 getPermissionLevel() const { return permissionLevel; } + u32 getPermissionFlags() const { return permissionFlags; } + bool getFlag(PermissionType permissionType) const; + private: + u8 permissionLevel; + u32 permissionFlags; + }; + + static const Permission ADMIN_PERMISSION(PERMISSION_LEVEL_ADMIN, { ALL_PERMISSION_TYPES }); + static const Permission REGULAR_USER_PERMISSION(PERMISSION_LEVEL_REGULAR_USER, { PermissionType::ADD_DATA }); +} diff --git a/include/RemoteUser.hpp b/include/RemoteUser.hpp index 770be61..181a4c4 100644 --- a/include/RemoteUser.hpp +++ b/include/RemoteUser.hpp @@ -7,9 +7,9 @@ namespace odhtdb class RemoteUser : public User { public: - static RemoteUser* create(const Signature::PublicKey &publicKey, const std::string &name) + static RemoteUser* create(const Signature::PublicKey &publicKey, const std::string &name, Group *group) { - return new RemoteUser(publicKey, name); + return new RemoteUser(publicKey, name, group); } const Signature::PublicKey& getPublicKey() const override @@ -17,7 +17,7 @@ namespace odhtdb return publicKey; } private: - RemoteUser(const Signature::PublicKey &_publicKey, const std::string &name) : User(name), publicKey(_publicKey){} + RemoteUser(const Signature::PublicKey &_publicKey, const std::string &name, Group *group) : User(name, group), publicKey(_publicKey){} private: Signature::PublicKey publicKey; }; diff --git a/include/User.hpp b/include/User.hpp index ab5872a..fb37876 100644 --- a/include/User.hpp +++ b/include/User.hpp @@ -3,9 +3,12 @@ #include "Signature.hpp" #include #include +#include namespace odhtdb { + class Group; + class UserNameTooLongException : public std::runtime_error { public: @@ -21,15 +24,15 @@ namespace odhtdb public: virtual ~User(){} + void addToGroup(Group *group); + const std::string& getName() const { return name; } + const std::vector& getGroups() const { return groups; } virtual const Signature::PublicKey& getPublicKey() const = 0; protected: - User(const std::string &_name) : name(_name) - { - if(name.size() > 255) - throw UserNameTooLongException(name); - } + User(const std::string &name, Group *group); private: std::string name; + std::vector groups; }; } diff --git a/project.conf b/project.conf index c283a7a..432af7b 100644 --- a/project.conf +++ b/project.conf @@ -12,3 +12,4 @@ libsodium = "1.0.16" ntpclient = "0.2.0" sibs-serializer = "0.2.0" boost-filesystem = "1.66.0" +boost-uuid = "1.66.0" 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); + } + } +} diff --git a/tests/main.cpp b/tests/main.cpp index 0218c8c..4d3bcc3 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -83,16 +83,20 @@ void testEncryption() int main() { printf("Starting tests...\n"); - LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba"); + LocalUser *localUser = LocalUser::create(Signature::KeyPair(), "dec05eba", nullptr); testSignData(localUser); testEncryption(); testHash(); // TODO: Setup local bootstrap node for tests Database database("bootstrap.ring.cx", 4222, "storage"); - auto databaseCreateResponse = database.create("dec05eba", "latenight"); - const char *data = "hello, world!"; - database.add(databaseCreateResponse, DataView{ (void*)data, strlen(data) }); + auto databaseCreateResponse = database.create("adminUserName", "latenight"); + DatabaseNode databaseNode(databaseCreateResponse->getNodeEncryptionKey(), databaseCreateResponse->getRequestHash()); + auto adminUser = (LocalUser*)databaseCreateResponse->getNodeAdminUser(); + database.addData(databaseNode, adminUser, DataView{ (void*)"hello, world!", 13 }); + database.addUserToGroup(databaseNode, adminUser, localUser->getName(), localUser->getPublicKey(), adminUser->getGroups()[0]); + localUser->addToGroup(adminUser->getGroups()[0]); + database.addData(databaseNode, localUser, DataView{ (void*)"hello, aaald!", 13 }); database.commit(); database.seed(databaseCreateResponse->getRequestHash(), databaseCreateResponse->getNodeEncryptionKey()); -- cgit v1.2.3