#include "UserProfile.h" #include "Cache.h" #include "ChatPage.h" #include "DeviceVerificationFlow.h" #include "Logging.h" #include "Utils.h" #include "mtx/responses/crypto.hpp" #include "timeline/TimelineModel.h" #include // only for debugging UserProfile::UserProfile(QString roomid, QString userid, TimelineModel *parent) : QObject(parent) , roomid_(roomid) , userid_(userid) , model(parent) { fetchDeviceList(this->userid_); } QHash DeviceInfoModel::roleNames() const { return { {DeviceId, "deviceId"}, {DeviceName, "deviceName"}, {VerificationStatus, "verificationStatus"}, }; } QVariant DeviceInfoModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0) return {}; switch (role) { case DeviceId: return deviceList_[index.row()].device_id; case DeviceName: return deviceList_[index.row()].display_name; case VerificationStatus: return QVariant::fromValue(deviceList_[index.row()].verification_status); default: return {}; } } void DeviceInfoModel::reset(const std::vector &deviceList) { beginResetModel(); this->deviceList_ = std::move(deviceList); endResetModel(); } DeviceInfoModel * UserProfile::deviceList() { return &this->deviceList_; } QString UserProfile::userid() { return this->userid_; } QString UserProfile::displayName() { return cache::displayName(roomid_, userid_); } QString UserProfile::avatarUrl() { return cache::avatarUrl(roomid_, userid_); } bool UserProfile::getUserStatus() { return isUserVerified; } void UserProfile::fetchDeviceList(const QString &userID) { auto localUser = utils::localUser(); mtx::requests::QueryKeys req; req.device_keys[userID.toStdString()] = {}; ChatPage::instance()->query_keys( req, [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {},{}", err->matrix_error.errcode, static_cast(err->status_code)); return; } if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) { nhlog::net()->warn("no devices retrieved {}", user_id); return; } // Finding if the User is Verified or not based on the Signatures mtx::requests::QueryKeys req; req.device_keys[utils::localUser().toStdString()] = {}; ChatPage::instance()->query_keys( req, [user_id, other_res = res, this](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { using namespace mtx; std::string local_user_id = utils::localUser().toStdString(); if (err) { nhlog::net()->warn("failed to query device keys: {},{}", err->matrix_error.errcode, static_cast(err->status_code)); return; } if (res.device_keys.empty() || (res.device_keys.find(local_user_id) == res.device_keys.end())) { nhlog::net()->warn("no devices retrieved {}", user_id); return; } std::vector deviceInfo; auto devices = other_res.device_keys.at(user_id); auto device_verified = cache::getVerifiedCache(user_id); if (device_verified.has_value()) { isUserVerified = device_verified.value().is_user_verified; } std::optional lmk, lsk, luk, mk, sk, uk; if (!res.master_keys.empty()) lmk = res.master_keys.at(local_user_id); if (!res.user_signing_keys.empty()) luk = res.user_signing_keys.at(local_user_id); if (!res.self_signing_keys.empty()) lsk = res.self_signing_keys.at(local_user_id); if (!other_res.master_keys.empty()) mk = other_res.master_keys.at(user_id); if (!other_res.user_signing_keys.empty()) uk = other_res.user_signing_keys.at(user_id); if (!other_res.self_signing_keys.empty()) sk = other_res.self_signing_keys.at(user_id); // First checking if the user is verified if (luk.has_value() && mk.has_value()) { // iterating through the public key of local user_signing keys for (auto sign_key : luk.value().keys) { // checking if the signatures are empty as "at" could // cause exceptions auto signs = mk->signatures; if (!signs.empty() && signs.find(local_user_id) != signs.end()) { auto sign = signs.at(local_user_id); try { isUserVerified = isUserVerified || (olm::client()->ed25519_verify_sig( sign_key.second, json(mk.value()), sign.at(sign_key.first))); } catch (std::out_of_range &) { isUserVerified = isUserVerified || false; } } } } for (const auto &d : devices) { auto device = d.second; verification::Status verified = verification::Status::UNVERIFIED; if (device_verified.has_value()) { if (std::find(device_verified->cross_verified.begin(), device_verified->cross_verified.end(), d.first) != device_verified->cross_verified.end()) verified = verification::Status::VERIFIED; if (std::find(device_verified->device_verified.begin(), device_verified->device_verified.end(), d.first) != device_verified->device_verified.end()) verified = verification::Status::VERIFIED; if (std::find(device_verified->device_blocked.begin(), device_verified->device_blocked.end(), d.first) != device_verified->device_blocked.end()) verified = verification::Status::BLOCKED; } else if (isUserVerified) { device_verified = DeviceVerifiedCache{}; } // won't check for already verified devices if (verified != verification::Status::VERIFIED && isUserVerified) { if ((sk.has_value()) && (!device.signatures.empty())) { for (auto sign_key : sk.value().keys) { auto signs = device.signatures.at(user_id); try { if (olm::client() ->ed25519_verify_sig( sign_key.second, json(device), signs.at( sign_key.first))) { verified = verification::Status:: VERIFIED; device_verified.value() .cross_verified .push_back(d.first); } } catch (std::out_of_range &) { } } } } if (device_verified.has_value()) { device_verified.value().is_user_verified = isUserVerified; cache::setVerifiedCache(user_id, device_verified.value()); } deviceInfo.push_back( {QString::fromStdString(d.first), QString::fromStdString( device.unsigned_info.device_display_name), verified}); } std::cout << (isUserVerified ? "Yes" : "No") << std::endl; std::sort(deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) { return a.device_id > b.device_id; }); this->deviceList_.queueReset(std::move(deviceInfo)); }); }); } void UserProfile::banUser() { ChatPage::instance()->banUser(this->userid_, ""); } // void ignoreUser(){ // } void UserProfile::kickUser() { ChatPage::instance()->kickUser(this->userid_, ""); } void UserProfile::startChat() { mtx::requests::CreateRoom req; req.preset = mtx::requests::Preset::PrivateChat; req.visibility = mtx::requests::Visibility::Private; if (utils::localUser() != this->userid_) req.invite = {this->userid_.toStdString()}; emit ChatPage::instance()->createRoom(req); } DeviceVerificationFlow * UserProfile::createFlow(bool isVerifyUser) { if (!isVerifyUser) return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice)); else { std::cout << "CHECKING IF IT TO START ROOM_VERIFICATION OR TO_DEVICE VERIFICATION" << std::endl; auto joined_rooms = cache::joinedRooms(); auto room_infos = cache::getRoomInfo(joined_rooms); for (std::string room_id : joined_rooms) { if ((room_infos[QString::fromStdString(room_id)].member_count == 2) && cache::isRoomEncrypted(room_id)) { auto room_members = cache::roomMembers(room_id); if (std::find(room_members.begin(), room_members.end(), (this->userid()).toStdString()) != room_members.end()) { std::cout << "FOUND A ENCRYPTED ROOM WITH THIS USER : " << room_id << std::endl; if (this->roomid_.toStdString() == room_id) { auto newflow = new DeviceVerificationFlow( this, DeviceVerificationFlow::Type::RoomMsg, this->model); return (std::move(newflow)); } else { std::cout << "FOUND A ENCRYPTED ROOM BUT CURRENTLY " "NOT IN THAT ROOM : " << room_id << std::endl; } } } } std::cout << "DIDN'T FIND A ENCRYPTED ROOM WITH THIS USER" << std::endl; return (new DeviceVerificationFlow(this, DeviceVerificationFlow::Type::ToDevice)); } }