376 lines
11 KiB
C++
376 lines
11 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "e2e_cloud/desktop/protected_groups_box.h"
|
|
|
|
#include "boxes/add_contact_box.h"
|
|
#include "e2e_cloud/desktop/desktop_service.h"
|
|
#include "e2e_cloud/desktop/protected_conversation_box.h"
|
|
#include "e2e_cloud/identity/account_identity.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_session.h"
|
|
#include "ui/widgets/fields/password_input.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_settings.h"
|
|
#include "styles/style_widgets.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace E2ECloud {
|
|
namespace {
|
|
|
|
inline constexpr auto kMinimumPasswordCharacters = 12;
|
|
|
|
[[nodiscard]] bool HasMinimumPasswordLength(const QString &password) {
|
|
auto count = 0;
|
|
for (auto i = 0; i != password.size();) {
|
|
const auto first = password.at(i++);
|
|
if (first.isHighSurrogate()
|
|
&& i != password.size()
|
|
&& password.at(i).isLowSurrogate()) {
|
|
++i;
|
|
}
|
|
if (++count >= kMinimumPasswordCharacters) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ClearPassword(QString &password) {
|
|
password.fill(QChar());
|
|
password.clear();
|
|
}
|
|
|
|
[[nodiscard]] QString VaultReadyText(const UnlockedCloudVault &vault) {
|
|
const auto accountId = DeriveAccountId(
|
|
vault.identity.credential,
|
|
OpenSslSha256Provider());
|
|
auto fingerprint = QString();
|
|
if (accountId) {
|
|
auto digest = Digest();
|
|
digest.bytes = accountId->bytes;
|
|
fingerprint = FormatSafetyCode(digest).value_or(QString());
|
|
}
|
|
return tr::lng_e2e_cloud_ready(
|
|
tr::now,
|
|
lt_chat_count,
|
|
QString::number(vault.conversations.size()),
|
|
lt_code,
|
|
fingerprint);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ProtectedGroupsBox::ProtectedGroupsBox(
|
|
QWidget*,
|
|
not_null<Window::SessionController*> controller)
|
|
: _controller(controller)
|
|
, _status(this, QString(), st::boxLabel)
|
|
, _password(
|
|
this,
|
|
st::defaultInputField,
|
|
tr::lng_e2e_cloud_password())
|
|
, _confirm(
|
|
this,
|
|
st::defaultInputField,
|
|
tr::lng_e2e_cloud_password_confirm())
|
|
, _manage(this, tr::lng_e2e_cloud_manage(), st::defaultBoxButton)
|
|
, _newGroup(this, tr::lng_e2e_cloud_new_group(), st::defaultBoxButton)
|
|
, _lock(this, tr::lng_e2e_cloud_lock(), st::defaultBoxButton) {
|
|
_manage->setClickedCallback([=] {
|
|
ShowProtectedGroupList(_controller);
|
|
});
|
|
_newGroup->setClickedCallback([=] { showGroupCreation(); });
|
|
_lock->setClickedCallback([=] {
|
|
_controller->session().e2eCloud().lock();
|
|
});
|
|
_manage->hide();
|
|
_newGroup->hide();
|
|
_lock->hide();
|
|
}
|
|
|
|
void ProtectedGroupsBox::prepare() {
|
|
setTitle(tr::lng_e2e_cloud_title());
|
|
_controller->session().e2eCloud().vaultStateValue(
|
|
) | rpl::on_next([=](DesktopVaultState) {
|
|
refresh();
|
|
}, lifetime());
|
|
_controller->session().e2eCloud().groupCreationStateValue(
|
|
) | rpl::on_next([=](DesktopGroupCreationState) {
|
|
refresh();
|
|
}, lifetime());
|
|
connect(_password, &Ui::MaskedInputField::submitted, [=] {
|
|
if (!_confirm->isHidden() && !_confirm->hasFocus()) {
|
|
_confirm->setFocusFast();
|
|
} else {
|
|
submit();
|
|
}
|
|
});
|
|
connect(_confirm, &Ui::MaskedInputField::submitted, [=] {
|
|
submit();
|
|
});
|
|
_controller->session().e2eCloud().ensureVaultDiscovery();
|
|
refresh();
|
|
}
|
|
|
|
void ProtectedGroupsBox::setInnerFocus() {
|
|
if (!_password->isHidden()) {
|
|
_password->setFocusFast();
|
|
}
|
|
}
|
|
|
|
void ProtectedGroupsBox::resizeEvent(QResizeEvent *event) {
|
|
BoxContent::resizeEvent(event);
|
|
updateControlsGeometry();
|
|
}
|
|
|
|
void ProtectedGroupsBox::updateControlsGeometry() {
|
|
const auto available = width()
|
|
- st::boxPadding.left()
|
|
- st::boxPadding.right();
|
|
auto top = st::boxPadding.top();
|
|
_status->moveToLeft(st::boxPadding.left(), top);
|
|
_status->resizeToWidth(available);
|
|
top += _status->height() + st::boxMediumSkip;
|
|
if (!_password->isHidden()) {
|
|
_password->resize(available, _password->height());
|
|
_password->moveToLeft(st::boxPadding.left(), top);
|
|
top += _password->height() + st::boxMediumSkip;
|
|
}
|
|
if (!_confirm->isHidden()) {
|
|
_confirm->resize(available, _confirm->height());
|
|
_confirm->moveToLeft(st::boxPadding.left(), top);
|
|
top += _confirm->height() + st::boxMediumSkip;
|
|
}
|
|
const auto placeButton = [&](
|
|
const object_ptr<Ui::RoundButton> &button) {
|
|
if (button->isHidden()) {
|
|
return;
|
|
}
|
|
button->resize(available, button->height());
|
|
button->moveToLeft(st::boxPadding.left(), top);
|
|
top += button->height() + st::boxMediumSkip;
|
|
};
|
|
placeButton(_manage);
|
|
placeButton(_newGroup);
|
|
placeButton(_lock);
|
|
setDimensions(st::boxWidth, top + st::boxPadding.bottom());
|
|
}
|
|
|
|
void ProtectedGroupsBox::refresh() {
|
|
const auto &service = _controller->session().e2eCloud();
|
|
const auto state = service.vaultState();
|
|
const auto passwordVisible = state == DesktopVaultState::Locked
|
|
|| state == DesktopVaultState::Missing
|
|
|| state == DesktopVaultState::WrongPasswordOrDamaged
|
|
|| state == DesktopVaultState::RetryableTransportError
|
|
|| state == DesktopVaultState::PermanentTransportError;
|
|
const auto confirmVisible = state == DesktopVaultState::Missing;
|
|
_password->setVisible(passwordVisible);
|
|
_confirm->setVisible(confirmVisible);
|
|
_password->setDisabled(
|
|
state == DesktopVaultState::Loading
|
|
|| state == DesktopVaultState::Creating);
|
|
_confirm->setDisabled(state == DesktopVaultState::Creating);
|
|
_manage->hide();
|
|
_newGroup->hide();
|
|
_lock->hide();
|
|
clearButtons();
|
|
switch (state) {
|
|
case DesktopVaultState::Uninitialized:
|
|
case DesktopVaultState::Discovering:
|
|
_status->setText(tr::lng_e2e_cloud_discovering(tr::now));
|
|
break;
|
|
case DesktopVaultState::Locked:
|
|
_password->setText(QString());
|
|
_confirm->setText(QString());
|
|
_status->setText(tr::lng_e2e_cloud_locked(tr::now));
|
|
addButton(tr::lng_e2e_cloud_unlock(), [=] { submit(); });
|
|
break;
|
|
case DesktopVaultState::Loading:
|
|
_status->setText(tr::lng_e2e_cloud_loading(tr::now));
|
|
break;
|
|
case DesktopVaultState::Missing:
|
|
_status->setText(tr::lng_e2e_cloud_missing(tr::now));
|
|
addButton(tr::lng_e2e_cloud_create(), [=] { submit(); });
|
|
break;
|
|
case DesktopVaultState::Creating:
|
|
_status->setText(tr::lng_e2e_cloud_creating(tr::now));
|
|
break;
|
|
case DesktopVaultState::Ready: {
|
|
_password->setText(QString());
|
|
_confirm->setText(QString());
|
|
const auto vault = service.vault();
|
|
_status->setText(vault
|
|
? VaultReadyText(*vault)
|
|
: tr::lng_e2e_cloud_damaged(tr::now));
|
|
const auto creation = service.groupCreationState();
|
|
if (!service.protectedGroups().empty()) {
|
|
_manage->show();
|
|
}
|
|
if (creation == DesktopGroupCreationState::RetryableTransportError
|
|
|| creation
|
|
== DesktopGroupCreationState::PermanentTransportError) {
|
|
addButton(tr::lng_e2e_cloud_retry(), [=] {
|
|
(void)_controller->session().e2eCloud()
|
|
.retryProtectedGroupCreation();
|
|
});
|
|
} else if (creation == DesktopGroupCreationState::Preparing
|
|
|| creation == DesktopGroupCreationState::UpdatingVault
|
|
|| creation == DesktopGroupCreationState::AwaitingAdmission
|
|
|| creation == DesktopGroupCreationState::AwaitingFreshness
|
|
|| creation
|
|
== DesktopGroupCreationState::PublishingBootstrap) {
|
|
_status->setText(tr::lng_e2e_cloud_group_publishing(tr::now));
|
|
} else if (creation == DesktopGroupCreationState::LocalFailure) {
|
|
_status->setText(tr::lng_e2e_cloud_group_failed(tr::now));
|
|
} else {
|
|
_newGroup->show();
|
|
}
|
|
_lock->show();
|
|
break;
|
|
}
|
|
case DesktopVaultState::WrongPasswordOrDamaged:
|
|
_status->setText(tr::lng_e2e_cloud_damaged(tr::now));
|
|
addButton(tr::lng_e2e_cloud_unlock(), [=] { submit(); });
|
|
break;
|
|
case DesktopVaultState::RetryableTransportError:
|
|
_status->setText(tr::lng_e2e_cloud_network_error(tr::now));
|
|
addButton(tr::lng_e2e_cloud_retry(), [=] {
|
|
if (!_controller->session().e2eCloud().retryCreateVault()) {
|
|
submit();
|
|
}
|
|
});
|
|
break;
|
|
case DesktopVaultState::PermanentTransportError:
|
|
_status->setText(tr::lng_e2e_cloud_transport_error(tr::now));
|
|
addButton(tr::lng_e2e_cloud_retry(), [=] { submit(); });
|
|
break;
|
|
case DesktopVaultState::DiscoveryRetryableError:
|
|
_status->setText(
|
|
tr::lng_e2e_cloud_discovery_network_error(tr::now));
|
|
addButton(tr::lng_e2e_cloud_retry(), [=] {
|
|
_controller->session().e2eCloud().ensureVaultDiscovery();
|
|
});
|
|
break;
|
|
case DesktopVaultState::DiscoveryPermanentError:
|
|
_status->setText(
|
|
tr::lng_e2e_cloud_discovery_transport_error(tr::now));
|
|
addButton(tr::lng_e2e_cloud_retry(), [=] {
|
|
_controller->session().e2eCloud().ensureVaultDiscovery();
|
|
});
|
|
break;
|
|
case DesktopVaultState::SecurityBlocked:
|
|
_status->setText(tr::lng_e2e_cloud_security_blocked(tr::now));
|
|
break;
|
|
}
|
|
addButton(tr::lng_close(), [=] { closeBox(); });
|
|
updateControlsGeometry();
|
|
if (passwordVisible
|
|
&& !_password->hasFocus()
|
|
&& !_confirm->hasFocus()) {
|
|
_password->setFocusFast();
|
|
}
|
|
}
|
|
|
|
void ProtectedGroupsBox::submit() {
|
|
auto &service = _controller->session().e2eCloud();
|
|
auto password = _password->getLastText();
|
|
if (password.isEmpty()) {
|
|
_password->setFocusFast();
|
|
_password->showError();
|
|
return;
|
|
}
|
|
const auto creating = service.vaultState() == DesktopVaultState::Missing;
|
|
if (creating && !HasMinimumPasswordLength(password)) {
|
|
_status->setText(
|
|
tr::lng_e2e_cloud_password_requirements(tr::now));
|
|
_password->setFocusFast();
|
|
_password->showError();
|
|
ClearPassword(password);
|
|
return;
|
|
}
|
|
auto passwordBytes = password.toUtf8();
|
|
auto started = false;
|
|
if (creating) {
|
|
auto confirmation = _confirm->getLastText();
|
|
if (confirmation.isEmpty() || confirmation != password) {
|
|
_confirm->setFocusFast();
|
|
_confirm->showError();
|
|
passwordBytes.fill('\0');
|
|
ClearPassword(password);
|
|
ClearPassword(confirmation);
|
|
return;
|
|
}
|
|
ClearPassword(confirmation);
|
|
started = service.createVault(passwordBytes);
|
|
} else {
|
|
started = service.unlock(passwordBytes);
|
|
}
|
|
passwordBytes.fill('\0');
|
|
ClearPassword(password);
|
|
if (started) {
|
|
_password->setText(QString());
|
|
_confirm->setText(QString());
|
|
}
|
|
}
|
|
|
|
void ProtectedGroupsBox::showGroupCreation() {
|
|
const auto controller = _controller;
|
|
uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
|
box->setTitle(tr::lng_e2e_cloud_history_policy());
|
|
const auto addPolicy = [=](
|
|
rpl::producer<QString> label,
|
|
HistoryAccessMode mode) {
|
|
const auto button = box->addRow(
|
|
object_ptr<Ui::SettingsButton>(
|
|
box,
|
|
std::move(label),
|
|
st::settingsButton),
|
|
style::al_top);
|
|
button->setClickedCallback([=] {
|
|
box->closeBox();
|
|
controller->uiShow()->showBox(
|
|
Box<GroupInfoBox>(
|
|
controller,
|
|
GroupInfoBox::Type::Megagroup,
|
|
QString(),
|
|
Fn<void(not_null<PeerData*>)>([
|
|
controller,
|
|
mode](not_null<PeerData*> peer) {
|
|
const auto started = controller->session()
|
|
.e2eCloud().createProtectedGroup(peer, {
|
|
.mode = mode,
|
|
.boundaryEventId = {},
|
|
});
|
|
controller->uiShow()->showToast(started
|
|
? tr::lng_e2e_cloud_group_publishing(
|
|
tr::now)
|
|
: tr::lng_e2e_cloud_group_failed(tr::now));
|
|
})),
|
|
Ui::LayerOption::KeepOther);
|
|
});
|
|
};
|
|
addPolicy(
|
|
tr::lng_e2e_cloud_history_none(),
|
|
HistoryAccessMode::None);
|
|
addPolicy(
|
|
tr::lng_e2e_cloud_history_from_join(),
|
|
HistoryAccessMode::FromJoin);
|
|
addPolicy(
|
|
tr::lng_e2e_cloud_history_full(),
|
|
HistoryAccessMode::Full);
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}));
|
|
}
|
|
|
|
} // namespace E2ECloud
|