diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 3d9c4b6a..b5a16f43 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -9,13 +9,14 @@
#include "Cache_p.h"
#include "ChatPage.h"
-#include "DeviceVerificationFlow.h"
#include "Logging.h"
#include "UserProfile.h"
#include "Utils.h"
+#include "encryption/DeviceVerificationFlow.h"
#include "mtx/responses/crypto.hpp"
#include "timeline/TimelineModel.h"
#include "timeline/TimelineViewManager.h"
+#include "ui/UIA.h"
UserProfile::UserProfile(QString roomid,
QString userid,
@@ -27,210 +28,276 @@ UserProfile::UserProfile(QString roomid,
, manager(manager_)
, model(parent)
{
- globalAvatarUrl = "";
+ globalAvatarUrl = "";
- connect(this,
- &UserProfile::globalUsernameRetrieved,
- this,
- &UserProfile::setGlobalUsername,
- Qt::QueuedConnection);
+ connect(this,
+ &UserProfile::globalUsernameRetrieved,
+ this,
+ &UserProfile::setGlobalUsername,
+ Qt::QueuedConnection);
+ connect(this, &UserProfile::verificationStatiChanged, &UserProfile::updateVerificationStatus);
- if (isGlobalUserProfile()) {
- getGlobalProfileData();
- }
+ if (isGlobalUserProfile()) {
+ getGlobalProfileData();
+ }
- if (!cache::client() || !cache::client()->isDatabaseReady() ||
- !ChatPage::instance()->timelineManager())
- return;
-
- connect(cache::client(),
- &Cache::verificationStatusChanged,
- this,
- [this](const std::string &user_id) {
- if (user_id != this->userid_.toStdString())
- return;
+ if (!cache::client() || !cache::client()->isDatabaseReady() ||
+ !ChatPage::instance()->timelineManager())
+ return;
- auto status = cache::verificationStatus(user_id);
- if (!status)
- return;
- this->isUserVerified = status->user_verified;
- emit userStatusChanged();
+ connect(
+ cache::client(), &Cache::verificationStatusChanged, this, [this](const std::string &user_id) {
+ if (user_id != this->userid_.toStdString())
+ return;
- for (auto &deviceInfo : deviceList_.deviceList_) {
- deviceInfo.verification_status =
- std::find(status->verified_devices.begin(),
- status->verified_devices.end(),
- deviceInfo.device_id.toStdString()) ==
- status->verified_devices.end()
- ? verification::UNVERIFIED
- : verification::VERIFIED;
- }
- deviceList_.reset(deviceList_.deviceList_);
- emit devicesChanged();
- });
- fetchDeviceList(this->userid_);
+ emit verificationStatiChanged();
+ });
+ fetchDeviceList(this->userid_);
}
QHash<int, QByteArray>
DeviceInfoModel::roleNames() const
{
- return {
- {DeviceId, "deviceId"},
- {DeviceName, "deviceName"},
- {VerificationStatus, "verificationStatus"},
- };
+ return {
+ {DeviceId, "deviceId"},
+ {DeviceName, "deviceName"},
+ {VerificationStatus, "verificationStatus"},
+ {LastIp, "lastIp"},
+ {LastTs, "lastTs"},
+ };
}
QVariant
DeviceInfoModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0)
- return {};
+ 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 {};
- }
+ 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);
+ case LastIp:
+ return deviceList_[index.row()].lastIp;
+ case LastTs:
+ return deviceList_[index.row()].lastTs;
+ default:
+ return {};
+ }
}
void
DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList)
{
- beginResetModel();
- this->deviceList_ = std::move(deviceList);
- endResetModel();
+ beginResetModel();
+ this->deviceList_ = std::move(deviceList);
+ endResetModel();
}
DeviceInfoModel *
UserProfile::deviceList()
{
- return &this->deviceList_;
+ return &this->deviceList_;
}
QString
UserProfile::userid()
{
- return this->userid_;
+ return this->userid_;
}
QString
UserProfile::displayName()
{
- return isGlobalUserProfile() ? globalUsername : cache::displayName(roomid_, userid_);
+ return isGlobalUserProfile() ? globalUsername : cache::displayName(roomid_, userid_);
}
QString
UserProfile::avatarUrl()
{
- return isGlobalUserProfile() ? globalAvatarUrl : cache::avatarUrl(roomid_, userid_);
+ return isGlobalUserProfile() ? globalAvatarUrl : cache::avatarUrl(roomid_, userid_);
}
bool
UserProfile::isGlobalUserProfile() const
{
- return roomid_ == "";
+ return roomid_ == "";
}
crypto::Trust
UserProfile::getUserStatus()
{
- return isUserVerified;
+ return isUserVerified;
}
bool
UserProfile::userVerificationEnabled() const
{
- return hasMasterKey;
+ return hasMasterKey;
}
bool
UserProfile::isSelf() const
{
- return this->userid_ == utils::localUser();
+ return this->userid_ == utils::localUser();
+}
+
+void
+UserProfile::signOutDevice(const QString &deviceID)
+{
+ http::client()->delete_device(
+ deviceID.toStdString(),
+ UIA::instance()->genericHandler(tr("Sign out device %1").arg(deviceID)),
+ [this, deviceID](mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::ui()->critical("Failure when attempting to sign out device {}",
+ deviceID.toStdString());
+ return;
+ }
+ nhlog::ui()->info("Device {} successfully signed out!", deviceID.toStdString());
+ // This is us. Let's update the interface accordingly
+ if (isSelf() && deviceID.toStdString() == ::http::client()->device_id()) {
+ ChatPage::instance()->dropToLoginPageCb(tr("You signed out this device."));
+ }
+ refreshDevices();
+ });
+}
+
+void
+UserProfile::refreshDevices()
+{
+ cache::client()->markUserKeysOutOfDate({this->userid_.toStdString()});
+ fetchDeviceList(this->userid_);
}
void
UserProfile::fetchDeviceList(const QString &userID)
{
- auto localUser = utils::localUser();
+ auto localUser = utils::localUser();
- if (!cache::client() || !cache::client()->isDatabaseReady())
- return;
+ if (!cache::client() || !cache::client()->isDatabaseReady())
+ return;
- cache::client()->query_keys(
- userID.toStdString(),
- [other_user_id = userID.toStdString(), this](const UserKeyCache &other_user_keys,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to query device keys: {},{}",
- mtx::errors::to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- return;
- }
+ cache::client()->query_keys(
+ userID.toStdString(),
+ [other_user_id = userID.toStdString(), this](const UserKeyCache &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to query device keys: {},{}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ }
+
+ // Ensure local key cache is up to date
+ cache::client()->query_keys(
+ utils::localUser().toStdString(),
+ [this](const UserKeyCache &, 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: {},{}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ static_cast<int>(err->status_code));
+ }
+
+ emit verificationStatiChanged();
+ });
+ });
+}
- // Ensure local key cache is up to date
- cache::client()->query_keys(
- utils::localUser().toStdString(),
- [other_user_id, other_user_keys, this](const UserKeyCache &,
- mtx::http::RequestErr err) {
- using namespace mtx;
- std::string local_user_id = utils::localUser().toStdString();
+void
+UserProfile::updateVerificationStatus()
+{
+ if (!cache::client() || !cache::client()->isDatabaseReady())
+ return;
- if (err) {
- nhlog::net()->warn(
- "failed to query device keys: {},{}",
- mtx::errors::to_string(err->matrix_error.errcode),
- static_cast<int>(err->status_code));
- return;
- }
+ auto user_keys = cache::client()->userKeys(userid_.toStdString());
+ if (!user_keys) {
+ this->hasMasterKey = false;
+ this->isUserVerified = crypto::Trust::Unverified;
+ this->deviceList_.reset({});
+ emit userStatusChanged();
+ return;
+ }
- this->hasMasterKey = !other_user_keys.master_keys.keys.empty();
+ this->hasMasterKey = !user_keys->master_keys.keys.empty();
- std::vector<DeviceInfo> deviceInfo;
- auto devices = other_user_keys.device_keys;
- auto verificationStatus =
- cache::client()->verificationStatus(other_user_id);
+ std::vector<DeviceInfo> deviceInfo;
+ auto devices = user_keys->device_keys;
+ auto verificationStatus = cache::client()->verificationStatus(userid_.toStdString());
- isUserVerified = verificationStatus.user_verified;
- emit userStatusChanged();
+ this->isUserVerified = verificationStatus.user_verified;
+ emit userStatusChanged();
- for (const auto &d : devices) {
- auto device = d.second;
- verification::Status verified =
- verification::Status::UNVERIFIED;
+ for (const auto &d : devices) {
+ auto device = d.second;
+ verification::Status verified =
+ std::find(verificationStatus.verified_devices.begin(),
+ verificationStatus.verified_devices.end(),
+ device.device_id) == verificationStatus.verified_devices.end()
+ ? verification::UNVERIFIED
+ : verification::VERIFIED;
- if (std::find(verificationStatus.verified_devices.begin(),
- verificationStatus.verified_devices.end(),
- device.device_id) !=
- verificationStatus.verified_devices.end() &&
- mtx::crypto::verify_identity_signature(
- device,
- DeviceId(device.device_id),
- UserId(other_user_id)))
- verified = verification::Status::VERIFIED;
+ if (isSelf() && device.device_id == ::http::client()->device_id())
+ verified = verification::Status::SELF;
- deviceInfo.push_back(
- {QString::fromStdString(d.first),
- QString::fromStdString(
- device.unsigned_info.device_display_name),
- verified});
- }
+ deviceInfo.push_back({QString::fromStdString(d.first),
+ QString::fromStdString(device.unsigned_info.device_display_name),
+ verified});
+ }
+
+ // For self, also query devices without keys
+ if (isSelf()) {
+ http::client()->query_devices(
+ [this, deviceInfo](const mtx::responses::QueryDevices &allDevs,
+ mtx::http::RequestErr err) mutable {
+ if (err) {
+ nhlog::net()->warn("failed to query devices: {} {}",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ this->deviceList_.queueReset(std::move(deviceInfo));
+ emit devicesChanged();
+ return;
+ }
+ for (const auto &d : allDevs.devices) {
+ // First, check if we already have an entry for this device
+ bool found = false;
+ for (auto &e : deviceInfo) {
+ if (e.device_id.toStdString() == d.device_id) {
+ found = true;
+ // Gottem! Let's fill in the blanks
+ e.lastIp = QString::fromStdString(d.last_seen_ip);
+ e.lastTs = d.last_seen_ts;
+ break;
+ }
+ }
+ // No entry? Let's add one.
+ if (!found) {
+ deviceInfo.push_back({QString::fromStdString(d.device_id),
+ QString::fromStdString(d.display_name),
+ verification::NOT_APPLICABLE,
+ QString::fromStdString(d.last_seen_ip),
+ d.last_seen_ts});
+ }
+ }
- this->deviceList_.queueReset(std::move(deviceInfo));
- emit devicesChanged();
- });
+ this->deviceList_.queueReset(std::move(deviceInfo));
+ emit devicesChanged();
});
+ return;
+ }
+
+ this->deviceList_.queueReset(std::move(deviceInfo));
+ emit devicesChanged();
}
void
UserProfile::banUser()
{
- ChatPage::instance()->banUser(this->userid_, "");
+ ChatPage::instance()->banUser(this->userid_, "");
}
// void ignoreUser(){
@@ -240,181 +307,193 @@ UserProfile::banUser()
void
UserProfile::kickUser()
{
- ChatPage::instance()->kickUser(this->userid_, "");
+ ChatPage::instance()->kickUser(this->userid_, "");
}
void
UserProfile::startChat()
{
- ChatPage::instance()->startChat(this->userid_);
+ ChatPage::instance()->startChat(this->userid_);
}
void
UserProfile::changeUsername(QString username)
{
- if (isGlobalUserProfile()) {
- // change global
- http::client()->set_displayname(
- username.toStdString(), [](mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("could not change username");
- return;
- }
- });
- } else {
- // change room username
- mtx::events::state::Member member;
- member.display_name = username.toStdString();
- member.avatar_url =
- cache::avatarUrl(roomid_,
- QString::fromStdString(http::client()->user_id().to_string()))
- .toStdString();
- member.membership = mtx::events::state::Membership::Join;
+ if (isGlobalUserProfile()) {
+ // change global
+ http::client()->set_displayname(username.toStdString(), [](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("could not change username");
+ return;
+ }
+ });
+ } else {
+ // change room username
+ mtx::events::state::Member member;
+ member.display_name = username.toStdString();
+ member.avatar_url =
+ cache::avatarUrl(roomid_, QString::fromStdString(http::client()->user_id().to_string()))
+ .toStdString();
+ member.membership = mtx::events::state::Membership::Join;
- updateRoomMemberState(std::move(member));
- }
+ updateRoomMemberState(std::move(member));
+ }
+}
+
+void
+UserProfile::changeDeviceName(QString deviceID, QString deviceName)
+{
+ http::client()->set_device_name(
+ deviceID.toStdString(), deviceName.toStdString(), [this](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("could not change device name");
+ return;
+ }
+ refreshDevices();
+ });
}
void
UserProfile::verify(QString device)
{
- if (!device.isEmpty())
- manager->verifyDevice(userid_, device);
- else {
- manager->verifyUser(userid_);
- }
+ if (!device.isEmpty())
+ manager->verificationManager()->verifyDevice(userid_, device);
+ else {
+ manager->verificationManager()->verifyUser(userid_);
+ }
}
void
UserProfile::unverify(QString device)
{
- cache::markDeviceUnverified(userid_.toStdString(), device.toStdString());
+ cache::markDeviceUnverified(userid_.toStdString(), device.toStdString());
}
void
UserProfile::setGlobalUsername(const QString &globalUser)
{
- globalUsername = globalUser;
- emit displayNameChanged();
+ globalUsername = globalUser;
+ emit displayNameChanged();
}
void
UserProfile::changeAvatar()
{
- const QString picturesFolder =
- QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
- const QString fileName = QFileDialog::getOpenFileName(
- nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
+ const QString picturesFolder =
+ QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+ const QString fileName = QFileDialog::getOpenFileName(
+ nullptr, tr("Select an avatar"), picturesFolder, tr("All Files (*)"));
- if (fileName.isEmpty())
- return;
+ if (fileName.isEmpty())
+ return;
- QMimeDatabase db;
- QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
+ QMimeDatabase db;
+ QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
- const auto format = mime.name().split("/")[0];
+ const auto format = mime.name().split("/")[0];
- QFile file{fileName, this};
- if (format != "image") {
- emit displayError(tr("The selected file is not an image"));
- return;
- }
+ QFile file{fileName, this};
+ if (format != "image") {
+ emit displayError(tr("The selected file is not an image"));
+ return;
+ }
- if (!file.open(QIODevice::ReadOnly)) {
- emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
- return;
- }
+ if (!file.open(QIODevice::ReadOnly)) {
+ emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
+ return;
+ }
+
+ const auto bin = file.peek(file.size());
+ const auto payload = std::string(bin.data(), bin.size());
- const auto bin = file.peek(file.size());
- const auto payload = std::string(bin.data(), bin.size());
+ isLoading_ = true;
+ emit loadingChanged();
- isLoading_ = true;
- emit loadingChanged();
+ // First we need to create a new mxc URI
+ // (i.e upload media to the Matrix content repository) for the new avatar.
+ http::client()->upload(
+ payload,
+ mime.name().toStdString(),
+ QFileInfo(fileName).fileName().toStdString(),
+ [this,
+ payload,
+ mimetype = mime.name().toStdString(),
+ size = payload.size(),
+ room_id = roomid_.toStdString(),
+ content = std::move(bin)](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::ui()->error("Failed to upload image", err->matrix_error.error);
+ return;
+ }
- // First we need to create a new mxc URI
- // (i.e upload media to the Matrix content repository) for the new avatar.
- http::client()->upload(
- payload,
- mime.name().toStdString(),
- QFileInfo(fileName).fileName().toStdString(),
- [this,
- payload,
- mimetype = mime.name().toStdString(),
- size = payload.size(),
- room_id = roomid_.toStdString(),
- content = std::move(bin)](const mtx::responses::ContentURI &res,
- mtx::http::RequestErr err) {
+ if (isGlobalUserProfile()) {
+ http::client()->set_avatar_url(res.content_uri, [this](mtx::http::RequestErr err) {
if (err) {
- nhlog::ui()->error("Failed to upload image", err->matrix_error.error);
- return;
+ nhlog::ui()->error("Failed to set user avatar url", err->matrix_error.error);
}
- if (isGlobalUserProfile()) {
- http::client()->set_avatar_url(
- res.content_uri, [this](mtx::http::RequestErr err) {
- if (err) {
- nhlog::ui()->error("Failed to set user avatar url",
- err->matrix_error.error);
- }
+ isLoading_ = false;
+ emit loadingChanged();
+ getGlobalProfileData();
+ });
+ } else {
+ // change room username
+ mtx::events::state::Member member;
+ member.display_name = cache::displayName(roomid_, userid_).toStdString();
+ member.avatar_url = res.content_uri;
+ member.membership = mtx::events::state::Membership::Join;
- isLoading_ = false;
- emit loadingChanged();
- getGlobalProfileData();
- });
- } else {
- // change room username
- mtx::events::state::Member member;
- member.display_name = cache::displayName(roomid_, userid_).toStdString();
- member.avatar_url = res.content_uri;
- member.membership = mtx::events::state::Membership::Join;
-
- updateRoomMemberState(std::move(member));
- }
- });
+ updateRoomMemberState(std::move(member));
+ }
+ });
}
void
UserProfile::updateRoomMemberState(mtx::events::state::Member member)
{
- http::client()->send_state_event(roomid_.toStdString(),
- http::client()->user_id().to_string(),
- member,
- [](mtx::responses::EventId, mtx::http::RequestErr err) {
- if (err)
- nhlog::net()->error(
- "Failed to update room member state : ",
- err->matrix_error.error);
- });
+ http::client()->send_state_event(
+ roomid_.toStdString(),
+ http::client()->user_id().to_string(),
+ member,
+ [](mtx::responses::EventId, mtx::http::RequestErr err) {
+ if (err)
+ nhlog::net()->error("Failed to update room member state : ", err->matrix_error.error);
+ });
}
void
UserProfile::updateAvatarUrl()
{
- isLoading_ = false;
- emit loadingChanged();
+ isLoading_ = false;
+ emit loadingChanged();
- emit avatarUrlChanged();
+ emit avatarUrlChanged();
}
bool
UserProfile::isLoading() const
{
- return isLoading_;
+ return isLoading_;
}
void
UserProfile::getGlobalProfileData()
{
- http::client()->get_profile(
- userid_.toStdString(),
- [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve own profile info");
- return;
- }
+ http::client()->get_profile(
+ userid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve profile info for {}", userid_.toStdString());
+ return;
+ }
- emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
- globalAvatarUrl = QString::fromStdString(res.avatar_url);
- emit avatarUrlChanged();
- });
+ emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
+ globalAvatarUrl = QString::fromStdString(res.avatar_url);
+ emit avatarUrlChanged();
+ });
+}
+
+void
+UserProfile::openGlobalProfile()
+{
+ emit manager->openGlobalUserProfile(userid_);
}
|