2018-03-18 12:51:14 +04:00
|
|
|
/*
|
|
|
|
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 "passport/passport_form_controller.h"
|
|
|
|
|
2018-03-21 08:35:32 +04:00
|
|
|
#include "passport/passport_encryption.h"
|
2018-03-31 05:45:40 +04:00
|
|
|
#include "passport/passport_panel_controller.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
#include "boxes/confirm_box.h"
|
2018-04-18 12:42:15 +04:00
|
|
|
#include "boxes/passcode_box.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
#include "lang/lang_keys.h"
|
2018-04-15 19:06:53 +04:00
|
|
|
#include "lang/lang_hardcoded.h"
|
2018-03-19 20:22:27 +04:00
|
|
|
#include "base/openssl_help.h"
|
2018-04-12 19:45:04 +04:00
|
|
|
#include "base/qthelp_url.h"
|
2018-07-10 19:41:11 +03:00
|
|
|
#include "data/data_session.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
#include "mainwindow.h"
|
2018-03-31 05:45:40 +04:00
|
|
|
#include "window/window_controller.h"
|
2018-04-12 19:45:04 +04:00
|
|
|
#include "core/click_handler_types.h"
|
2018-04-13 22:14:14 +04:00
|
|
|
#include "ui/toast/toast.h"
|
2018-03-25 15:37:57 +04:00
|
|
|
#include "auth_session.h"
|
|
|
|
#include "storage/localimageloader.h"
|
2018-03-29 01:01:28 +04:00
|
|
|
#include "storage/localstorage.h"
|
2018-03-25 15:37:57 +04:00
|
|
|
#include "storage/file_upload.h"
|
|
|
|
#include "storage/file_download.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
|
|
|
|
namespace Passport {
|
2018-03-29 00:40:42 +04:00
|
|
|
namespace {
|
|
|
|
|
2018-04-13 14:54:17 +04:00
|
|
|
constexpr auto kDocumentScansLimit = 20;
|
2018-04-17 15:26:11 +04:00
|
|
|
constexpr auto kShortPollTimeout = TimeMs(3000);
|
2018-07-10 19:41:11 +03:00
|
|
|
constexpr auto kRememberCredentialsDelay = TimeMs(1800 * 1000);
|
2018-04-13 14:54:17 +04:00
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
bool ForwardServiceErrorRequired(const QString &error) {
|
|
|
|
return (error == qstr("BOT_INVALID"))
|
|
|
|
|| (error == qstr("PUBLIC_KEY_REQUIRED"))
|
|
|
|
|| (error == qstr("PUBLIC_KEY_INVALID"))
|
|
|
|
|| (error == qstr("SCOPE_EMPTY"))
|
|
|
|
|| (error == qstr("PAYLOAD_EMPTY"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveErrorRequiresRestart(const QString &error) {
|
|
|
|
return (error == qstr("PASSWORD_REQUIRED"))
|
|
|
|
|| (error == qstr("SECURE_SECRET_REQUIRED"))
|
|
|
|
|| (error == qstr("SECURE_SECRET_INVALID"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AcceptErrorRequiresRestart(const QString &error) {
|
|
|
|
return (error == qstr("PASSWORD_REQUIRED"))
|
|
|
|
|| (error == qstr("SECURE_SECRET_REQUIRED"))
|
|
|
|
|| (error == qstr("SECURE_VALUE_EMPTY"))
|
|
|
|
|| (error == qstr("SECURE_VALUE_HASH_INVALID"));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<QString, QString> GetTexts(const ValueMap &map) {
|
|
|
|
auto result = std::map<QString, QString>();
|
|
|
|
for (const auto &[key, value] : map.fields) {
|
|
|
|
result[key] = value.text;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
QImage ReadImage(bytes::const_span buffer) {
|
|
|
|
return App::readImage(QByteArray::fromRawData(
|
|
|
|
reinterpret_cast<const char*>(buffer.data()),
|
|
|
|
buffer.size()));
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
Value::Type ConvertType(const MTPSecureValueType &type) {
|
|
|
|
using Type = Value::Type;
|
|
|
|
switch (type.type()) {
|
2018-05-12 01:55:56 +03:00
|
|
|
case mtpc_secureValueTypePersonalDetails:
|
|
|
|
return Type::PersonalDetails;
|
|
|
|
case mtpc_secureValueTypePassport:
|
|
|
|
return Type::Passport;
|
|
|
|
case mtpc_secureValueTypeDriverLicense:
|
|
|
|
return Type::DriverLicense;
|
|
|
|
case mtpc_secureValueTypeIdentityCard:
|
|
|
|
return Type::IdentityCard;
|
|
|
|
case mtpc_secureValueTypeInternalPassport:
|
|
|
|
return Type::InternalPassport;
|
|
|
|
case mtpc_secureValueTypeAddress:
|
|
|
|
return Type::Address;
|
|
|
|
case mtpc_secureValueTypeUtilityBill:
|
|
|
|
return Type::UtilityBill;
|
|
|
|
case mtpc_secureValueTypeBankStatement:
|
|
|
|
return Type::BankStatement;
|
|
|
|
case mtpc_secureValueTypeRentalAgreement:
|
|
|
|
return Type::RentalAgreement;
|
|
|
|
case mtpc_secureValueTypePassportRegistration:
|
|
|
|
return Type::PassportRegistration;
|
|
|
|
case mtpc_secureValueTypeTemporaryRegistration:
|
|
|
|
return Type::TemporaryRegistration;
|
|
|
|
case mtpc_secureValueTypePhone:
|
|
|
|
return Type::Phone;
|
|
|
|
case mtpc_secureValueTypeEmail:
|
|
|
|
return Type::Email;
|
2018-04-03 22:24:31 +04:00
|
|
|
}
|
|
|
|
Unexpected("Type in secureValueType type.");
|
|
|
|
};
|
|
|
|
|
2018-04-12 19:45:04 +04:00
|
|
|
MTPSecureValueType ConvertType(Value::Type type) {
|
2018-05-12 01:55:56 +03:00
|
|
|
using Type = Value::Type;
|
2018-04-12 19:45:04 +04:00
|
|
|
switch (type) {
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::PersonalDetails:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypePersonalDetails();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::Passport:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypePassport();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::DriverLicense:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeDriverLicense();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::IdentityCard:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeIdentityCard();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::InternalPassport:
|
|
|
|
return MTP_secureValueTypeInternalPassport();
|
|
|
|
case Type::Address:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeAddress();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::UtilityBill:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeUtilityBill();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::BankStatement:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeBankStatement();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::RentalAgreement:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeRentalAgreement();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::PassportRegistration:
|
|
|
|
return MTP_secureValueTypePassportRegistration();
|
|
|
|
case Type::TemporaryRegistration:
|
|
|
|
return MTP_secureValueTypeTemporaryRegistration();
|
|
|
|
case Type::Phone:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypePhone();
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::Email:
|
2018-04-12 19:45:04 +04:00
|
|
|
return MTP_secureValueTypeEmail();
|
|
|
|
}
|
|
|
|
Unexpected("Type in FormController::submit.");
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CollectToRequestedRow(
|
|
|
|
RequestedRow &row,
|
|
|
|
const MTPSecureRequiredType &data) {
|
|
|
|
data.match([&](const MTPDsecureRequiredType &data) {
|
|
|
|
row.values.emplace_back(ConvertType(data.vtype));
|
|
|
|
auto &value = row.values.back();
|
|
|
|
value.selfieRequired = data.is_selfie_required();
|
|
|
|
value.translationRequired = data.is_translation_required();
|
|
|
|
value.nativeNames = data.is_native_names();
|
|
|
|
}, [&](const MTPDsecureRequiredTypeOneOf &data) {
|
|
|
|
row.values.reserve(row.values.size() + data.vtypes.v.size());
|
|
|
|
for (const auto &one : data.vtypes.v) {
|
|
|
|
CollectToRequestedRow(row, one);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
RequestedRow CollectRequestedRow(const MTPSecureRequiredType &data) {
|
|
|
|
auto result = RequestedRow();
|
|
|
|
CollectToRequestedRow(result, data);
|
|
|
|
return result;
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
|
|
|
|
QJsonObject GetJSONFromMap(
|
2018-04-13 22:14:14 +04:00
|
|
|
const std::map<QString, bytes::const_span> &map) {
|
2018-04-12 19:45:04 +04:00
|
|
|
auto result = QJsonObject();
|
|
|
|
for (const auto &[key, value] : map) {
|
|
|
|
const auto raw = QByteArray::fromRawData(
|
|
|
|
reinterpret_cast<const char*>(value.data()),
|
|
|
|
value.size());
|
|
|
|
result.insert(key, QString::fromUtf8(raw.toBase64()));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject GetJSONFromFile(const File &file) {
|
|
|
|
return GetJSONFromMap({
|
|
|
|
{ "file_hash", file.hash },
|
|
|
|
{ "secret", file.secret }
|
2018-04-13 22:14:14 +04:00
|
|
|
});
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
FormRequest PreprocessRequest(const FormRequest &request) {
|
|
|
|
auto result = request;
|
|
|
|
result.publicKey.replace("\r\n", "\n");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ValueCredentialsKey(Value::Type type) {
|
2018-05-12 01:55:56 +03:00
|
|
|
using Type = Value::Type;
|
2018-04-12 19:45:04 +04:00
|
|
|
switch (type) {
|
2018-05-12 01:55:56 +03:00
|
|
|
case Type::PersonalDetails: return "personal_details";
|
|
|
|
case Type::Passport: return "passport";
|
|
|
|
case Type::DriverLicense: return "driver_license";
|
|
|
|
case Type::IdentityCard: return "identity_card";
|
|
|
|
case Type::InternalPassport: return "internal_passport";
|
|
|
|
case Type::Address: return "address";
|
|
|
|
case Type::UtilityBill: return "utility_bill";
|
|
|
|
case Type::BankStatement: return "bank_statement";
|
|
|
|
case Type::RentalAgreement: return "rental_agreement";
|
|
|
|
case Type::PassportRegistration: return "passport_registration";
|
|
|
|
case Type::TemporaryRegistration: return "temporary_registration";
|
|
|
|
case Type::Phone:
|
|
|
|
case Type::Email: return QString();
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
Unexpected("Type in ValueCredentialsKey.");
|
|
|
|
}
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
QString SpecialScanCredentialsKey(SpecialFile type) {
|
|
|
|
switch (type) {
|
|
|
|
case SpecialFile::FrontSide: return "front_side";
|
|
|
|
case SpecialFile::ReverseSide: return "reverse_side";
|
|
|
|
case SpecialFile::Selfie: return "selfie";
|
|
|
|
}
|
|
|
|
Unexpected("Type in SpecialScanCredentialsKey.");
|
|
|
|
}
|
|
|
|
|
2018-07-31 22:51:06 +03:00
|
|
|
QString ValidateUrl(const QString &url) {
|
|
|
|
const auto result = qthelp::validate_url(url);
|
|
|
|
return result.startsWith("tg://", Qt::CaseInsensitive)
|
|
|
|
? QString()
|
|
|
|
: result;
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
} // namespace
|
2018-03-18 12:51:14 +04:00
|
|
|
|
|
|
|
FormRequest::FormRequest(
|
|
|
|
UserId botId,
|
2018-03-27 17:00:13 +04:00
|
|
|
const QString &scope,
|
2018-03-18 12:51:14 +04:00
|
|
|
const QString &callbackUrl,
|
2018-04-12 19:45:04 +04:00
|
|
|
const QString &publicKey,
|
2018-04-17 21:54:52 +04:00
|
|
|
const QString &payload,
|
|
|
|
const QString &errors)
|
2018-04-15 10:47:15 +04:00
|
|
|
: botId(botId)
|
|
|
|
, scope(scope)
|
2018-07-31 22:51:06 +03:00
|
|
|
, callbackUrl(ValidateUrl(callbackUrl))
|
2018-04-15 10:47:15 +04:00
|
|
|
, publicKey(publicKey)
|
2018-04-17 21:54:52 +04:00
|
|
|
, payload(payload)
|
|
|
|
, errors(errors) {
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
EditFile::EditFile(
|
2018-04-03 22:24:31 +04:00
|
|
|
not_null<const Value*> value,
|
2018-03-25 15:37:57 +04:00
|
|
|
const File &fields,
|
2018-03-29 18:44:34 +04:00
|
|
|
std::unique_ptr<UploadScanData> &&uploadData)
|
2018-04-15 19:06:53 +04:00
|
|
|
: value(value)
|
|
|
|
, fields(std::move(fields))
|
|
|
|
, uploadData(std::move(uploadData))
|
|
|
|
, guard(std::make_shared<bool>(true)) {
|
2018-03-29 23:49:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
UploadScanDataPointer::UploadScanDataPointer(
|
|
|
|
std::unique_ptr<UploadScanData> &&value)
|
2018-04-15 19:06:53 +04:00
|
|
|
: _value(std::move(value)) {
|
2018-03-29 23:49:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
UploadScanDataPointer::UploadScanDataPointer(
|
|
|
|
UploadScanDataPointer &&other) = default;
|
|
|
|
|
|
|
|
UploadScanDataPointer &UploadScanDataPointer::operator=(
|
|
|
|
UploadScanDataPointer &&other) = default;
|
|
|
|
|
|
|
|
UploadScanDataPointer::~UploadScanDataPointer() {
|
|
|
|
if (const auto value = _value.get()) {
|
|
|
|
if (const auto fullId = value->fullId) {
|
|
|
|
Auth().uploader().cancel(fullId);
|
|
|
|
}
|
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
UploadScanData *UploadScanDataPointer::get() const {
|
|
|
|
return _value.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
UploadScanDataPointer::operator UploadScanData*() const {
|
|
|
|
return _value.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
UploadScanDataPointer::operator bool() const {
|
|
|
|
return _value.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
UploadScanData *UploadScanDataPointer::operator->() const {
|
|
|
|
return _value.get();
|
|
|
|
}
|
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
RequestedValue::RequestedValue(Value::Type type) : type(type) {
|
|
|
|
}
|
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
Value::Value(Type type) : type(type) {
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
bool Value::requiresSpecialScan(SpecialFile type) const {
|
2018-05-12 01:55:56 +03:00
|
|
|
switch (type) {
|
|
|
|
case SpecialFile::FrontSide:
|
|
|
|
return (this->type == Type::Passport)
|
|
|
|
|| (this->type == Type::DriverLicense)
|
|
|
|
|| (this->type == Type::IdentityCard)
|
|
|
|
|| (this->type == Type::InternalPassport);
|
|
|
|
case SpecialFile::ReverseSide:
|
|
|
|
return (this->type == Type::DriverLicense)
|
|
|
|
|| (this->type == Type::IdentityCard);
|
|
|
|
case SpecialFile::Selfie:
|
|
|
|
return selfieRequired;
|
|
|
|
}
|
|
|
|
Unexpected("Special scan type in requiresSpecialScan.");
|
|
|
|
}
|
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
void Value::fillDataFrom(Value &&other) {
|
|
|
|
const auto savedSelfieRequired = selfieRequired;
|
|
|
|
const auto savedTranslationRequired = translationRequired;
|
|
|
|
const auto savedNativeNames = nativeNames;
|
|
|
|
const auto savedEditScreens = editScreens;
|
|
|
|
|
|
|
|
*this = std::move(other);
|
|
|
|
|
|
|
|
selfieRequired = savedSelfieRequired;
|
|
|
|
translationRequired = savedTranslationRequired;
|
|
|
|
nativeNames = savedNativeNames;
|
|
|
|
editScreens = savedEditScreens;
|
|
|
|
}
|
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
bool Value::scansAreFilled() const {
|
|
|
|
if (!requiresSpecialScan(SpecialFile::FrontSide) && scans.empty()) {
|
|
|
|
return false;
|
|
|
|
} else if (translationRequired && translations.empty()) {
|
|
|
|
return false;
|
2018-05-12 01:55:56 +03:00
|
|
|
}
|
|
|
|
const auto types = {
|
|
|
|
SpecialFile::FrontSide,
|
|
|
|
SpecialFile::ReverseSide,
|
|
|
|
SpecialFile::Selfie
|
|
|
|
};
|
|
|
|
for (const auto type : types) {
|
2018-08-12 22:48:43 +03:00
|
|
|
if (requiresSpecialScan(type)
|
2018-05-12 01:55:56 +03:00
|
|
|
&& (specialScans.find(type) == end(specialScans))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
FormController::FormController(
|
|
|
|
not_null<Window::Controller*> controller,
|
|
|
|
const FormRequest &request)
|
2018-04-15 10:47:15 +04:00
|
|
|
: _controller(controller)
|
|
|
|
, _request(PreprocessRequest(request))
|
2018-04-17 15:26:11 +04:00
|
|
|
, _shortPollTimer([=] { reloadPassword(); })
|
2018-04-15 10:47:15 +04:00
|
|
|
, _view(std::make_unique<PanelController>(this)) {
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::show() {
|
|
|
|
requestForm();
|
|
|
|
requestPassword();
|
|
|
|
}
|
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
UserData *FormController::bot() const {
|
|
|
|
return _bot;
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
QString FormController::privacyPolicyUrl() const {
|
|
|
|
return _form.privacyPolicyUrl;
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
bytes::vector FormController::passwordHashForAuth(
|
2018-04-15 10:47:15 +04:00
|
|
|
bytes::const_span password) const {
|
2018-08-10 22:19:46 +03:00
|
|
|
return Core::ComputeCloudPasswordHash(_password.request.algo, password);
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
|
2018-04-13 21:42:28 +04:00
|
|
|
auto FormController::prepareFinalData() -> FinalData {
|
2018-04-15 19:06:53 +04:00
|
|
|
auto errors = std::vector<not_null<const Value*>>();
|
2018-04-12 19:45:04 +04:00
|
|
|
auto hashes = QVector<MTPSecureValueHash>();
|
|
|
|
auto secureData = QJsonObject();
|
|
|
|
const auto addValueToJSON = [&](
|
2018-04-17 21:54:52 +04:00
|
|
|
const QString &key,
|
|
|
|
not_null<const Value*> value) {
|
2018-04-12 19:45:04 +04:00
|
|
|
auto object = QJsonObject();
|
|
|
|
if (!value->data.parsed.fields.empty()) {
|
|
|
|
object.insert("data", GetJSONFromMap({
|
|
|
|
{ "data_hash", value->data.hash },
|
|
|
|
{ "secret", value->data.secret }
|
2018-04-15 10:47:15 +04:00
|
|
|
}));
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
if (!value->scans.empty()) {
|
|
|
|
auto files = QJsonArray();
|
|
|
|
for (const auto &scan : value->scans) {
|
|
|
|
files.append(GetJSONFromFile(scan));
|
|
|
|
}
|
|
|
|
object.insert("files", files);
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (const auto &[type, scan] : value->specialScans) {
|
2018-08-12 22:48:43 +03:00
|
|
|
if (value->requiresSpecialScan(type)) {
|
2018-05-12 01:55:56 +03:00
|
|
|
object.insert(
|
|
|
|
SpecialScanCredentialsKey(type),
|
|
|
|
GetJSONFromFile(scan));
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
secureData.insert(key, object);
|
|
|
|
};
|
|
|
|
const auto addValue = [&](not_null<const Value*> value) {
|
|
|
|
hashes.push_back(MTP_secureValueHash(
|
|
|
|
ConvertType(value->type),
|
|
|
|
MTP_bytes(value->submitHash)));
|
|
|
|
const auto key = ValueCredentialsKey(value->type);
|
|
|
|
if (!key.isEmpty()) {
|
|
|
|
addValueToJSON(key, value);
|
|
|
|
}
|
|
|
|
};
|
2018-08-12 22:48:43 +03:00
|
|
|
const auto scopes = ComputeScopes(_form);
|
2018-04-12 19:45:04 +04:00
|
|
|
for (const auto &scope : scopes) {
|
2018-04-17 21:54:52 +04:00
|
|
|
const auto row = ComputeScopeRow(scope);
|
|
|
|
if (row.ready.isEmpty() || !row.error.isEmpty()) {
|
2018-08-12 22:48:43 +03:00
|
|
|
errors.push_back(scope.details
|
|
|
|
? scope.details
|
|
|
|
: scope.documents[0].get());
|
2018-04-13 14:54:17 +04:00
|
|
|
continue;
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
if (scope.details) {
|
|
|
|
addValue(scope.details);
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
if (!scope.documents.empty()) {
|
|
|
|
for (const auto &document : scope.documents) {
|
2018-08-12 22:48:43 +03:00
|
|
|
if (document->scansAreFilled()) {
|
2018-04-12 19:45:04 +04:00
|
|
|
addValue(document);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto json = QJsonObject();
|
2018-04-15 19:06:53 +04:00
|
|
|
if (errors.empty()) {
|
|
|
|
json.insert("secure_data", secureData);
|
|
|
|
json.insert("payload", _request.payload);
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
|
|
|
|
return {
|
|
|
|
hashes,
|
2018-04-15 19:06:53 +04:00
|
|
|
QJsonDocument(json).toJson(QJsonDocument::Compact),
|
|
|
|
errors
|
2018-04-12 19:45:04 +04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
std::vector<not_null<const Value*>> FormController::submitGetErrors() {
|
2018-04-13 22:14:14 +04:00
|
|
|
if (_submitRequestId || _submitSuccess|| _cancelled) {
|
2018-04-15 19:06:53 +04:00
|
|
|
return {};
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto prepared = prepareFinalData();
|
2018-04-15 19:06:53 +04:00
|
|
|
if (!prepared.errors.empty()) {
|
|
|
|
return prepared.errors;
|
2018-04-13 14:54:17 +04:00
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
const auto credentialsEncryptedData = EncryptData(
|
|
|
|
bytes::make_span(prepared.credentials));
|
|
|
|
const auto credentialsEncryptedSecret = EncryptCredentialsSecret(
|
|
|
|
credentialsEncryptedData.secret,
|
|
|
|
bytes::make_span(_request.publicKey.toUtf8()));
|
|
|
|
|
|
|
|
_submitRequestId = request(MTPaccount_AcceptAuthorization(
|
|
|
|
MTP_int(_request.botId),
|
|
|
|
MTP_string(_request.scope),
|
|
|
|
MTP_string(_request.publicKey),
|
|
|
|
MTP_vector<MTPSecureValueHash>(prepared.hashes),
|
|
|
|
MTP_secureCredentialsEncrypted(
|
|
|
|
MTP_bytes(credentialsEncryptedData.bytes),
|
|
|
|
MTP_bytes(credentialsEncryptedData.hash),
|
|
|
|
MTP_bytes(credentialsEncryptedSecret))
|
|
|
|
)).done([=](const MTPBool &result) {
|
2018-04-13 22:14:14 +04:00
|
|
|
_submitRequestId = 0;
|
|
|
|
_submitSuccess = true;
|
|
|
|
|
|
|
|
_view->showToast(lang(lng_passport_success));
|
|
|
|
|
|
|
|
App::CallDelayed(
|
|
|
|
Ui::Toast::DefaultDuration + st::toastFadeOutDuration,
|
|
|
|
this,
|
|
|
|
[=] { cancel(); });
|
2018-04-12 19:45:04 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
2018-04-13 22:14:14 +04:00
|
|
|
_submitRequestId = 0;
|
2018-04-15 19:06:53 +04:00
|
|
|
if (AcceptErrorRequiresRestart(error.type())) {
|
|
|
|
suggestRestart();
|
|
|
|
} else {
|
|
|
|
_view->show(Box<InformBox>(
|
|
|
|
Lang::Hard::SecureAcceptError() + "\n" + error.type()));
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
}).send();
|
2018-04-15 19:06:53 +04:00
|
|
|
|
|
|
|
return {};
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
|
|
|
|
2018-08-10 22:19:46 +03:00
|
|
|
void FormController::checkPasswordHash(
|
|
|
|
mtpRequestId &guard,
|
|
|
|
bytes::vector hash,
|
|
|
|
PasswordCheckCallback callback) {
|
|
|
|
_passwordCheckHash = std::move(hash);
|
|
|
|
_passwordCheckCallback = std::move(callback);
|
|
|
|
if (_password.request.id) {
|
|
|
|
passwordChecked();
|
|
|
|
} else {
|
|
|
|
requestPasswordData(guard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::passwordChecked() {
|
|
|
|
if (!_password.request || !_password.request.id) {
|
|
|
|
return passwordServerError();
|
|
|
|
}
|
|
|
|
const auto check = Core::ComputeCloudPasswordCheck(
|
|
|
|
_password.request,
|
|
|
|
_passwordCheckHash);
|
|
|
|
if (!check) {
|
|
|
|
return passwordServerError();
|
|
|
|
}
|
|
|
|
_password.request.id = 0;
|
|
|
|
_passwordCheckCallback(check);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::requestPasswordData(mtpRequestId &guard) {
|
|
|
|
if (!_passwordCheckCallback) {
|
|
|
|
return passwordServerError();
|
|
|
|
}
|
|
|
|
|
|
|
|
request(base::take(guard)).cancel();
|
|
|
|
guard = request(
|
|
|
|
MTPaccount_GetPassword()
|
|
|
|
).done([=, &guard](const MTPaccount_Password &result) {
|
|
|
|
guard = 0;
|
|
|
|
result.match([&](const MTPDaccount_password &data) {
|
|
|
|
_password.request = Core::ParseCloudPasswordCheckRequest(data);
|
|
|
|
passwordChecked();
|
|
|
|
});
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-07-10 19:41:11 +03:00
|
|
|
void FormController::submitPassword(const QByteArray &password) {
|
2018-08-10 22:19:46 +03:00
|
|
|
Expects(!!_password.request);
|
2018-03-19 20:22:27 +04:00
|
|
|
|
2018-07-10 19:41:11 +03:00
|
|
|
const auto submitSaved = !base::take(_savedPasswordValue).isEmpty();
|
2018-03-19 20:22:27 +04:00
|
|
|
if (_passwordCheckRequestId) {
|
|
|
|
return;
|
2018-03-21 08:35:32 +04:00
|
|
|
} else if (password.isEmpty()) {
|
|
|
|
_passwordError.fire(QString());
|
2018-07-10 19:41:11 +03:00
|
|
|
return;
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
2018-08-10 22:19:46 +03:00
|
|
|
const auto callback = [=](const Core::CloudPasswordResult &check) {
|
|
|
|
submitPassword(check, password, submitSaved);
|
|
|
|
};
|
|
|
|
checkPasswordHash(
|
|
|
|
_passwordCheckRequestId,
|
|
|
|
passwordHashForAuth(bytes::make_span(password)),
|
|
|
|
callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::submitPassword(
|
|
|
|
const Core::CloudPasswordResult &check,
|
|
|
|
const QByteArray &password,
|
|
|
|
bool submitSaved) {
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
2018-08-10 22:19:46 +03:00
|
|
|
check.result
|
2018-03-19 20:22:27 +04:00
|
|
|
)).handleFloodErrors(
|
|
|
|
).done([=](const MTPaccount_PasswordSettings &result) {
|
|
|
|
Expects(result.type() == mtpc_account_passwordSettings);
|
|
|
|
|
|
|
|
_passwordCheckRequestId = 0;
|
2018-07-10 19:41:11 +03:00
|
|
|
_savedPasswordValue = QByteArray();
|
2018-08-04 00:48:00 +03:00
|
|
|
const auto &data = result.c_account_passwordSettings();
|
2018-03-27 17:00:13 +04:00
|
|
|
_password.confirmedEmail = qs(data.vemail);
|
2018-08-04 00:48:00 +03:00
|
|
|
if (data.has_secure_settings()) {
|
|
|
|
const auto &wrapped = data.vsecure_settings;
|
|
|
|
const auto &settings = wrapped.c_secureSecretSettings();
|
|
|
|
const auto algo = Core::ParseSecureSecretAlgo(
|
|
|
|
settings.vsecure_algo);
|
|
|
|
if (!algo) {
|
|
|
|
_view->showUpdateAppBox();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto hashForSecret = Core::ComputeSecureSecretHash(
|
|
|
|
algo,
|
|
|
|
bytes::make_span(password));
|
|
|
|
validateSecureSecret(
|
|
|
|
bytes::make_span(settings.vsecure_secret.v),
|
|
|
|
hashForSecret,
|
|
|
|
bytes::make_span(password),
|
|
|
|
settings.vsecure_secret_id.v);
|
|
|
|
if (!_secret.empty()) {
|
|
|
|
auto saved = SavedCredentials();
|
2018-08-10 22:19:46 +03:00
|
|
|
saved.hashForAuth = base::take(_passwordCheckHash);
|
2018-08-04 00:48:00 +03:00
|
|
|
saved.hashForSecret = hashForSecret;
|
|
|
|
saved.secretId = _secretId;
|
|
|
|
Auth().data().rememberPassportCredentials(
|
|
|
|
std::move(saved),
|
|
|
|
kRememberCredentialsDelay);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
validateSecureSecret(
|
|
|
|
bytes::const_span(), // secure_secret
|
|
|
|
bytes::const_span(), // hash for secret
|
|
|
|
bytes::make_span(password),
|
|
|
|
0); // secure_secret_id
|
2018-07-10 19:41:11 +03:00
|
|
|
}
|
2018-03-19 20:22:27 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_passwordCheckRequestId = 0;
|
2018-08-10 22:19:46 +03:00
|
|
|
if (error.type() == qstr("SRP_ID_INVALID")) {
|
|
|
|
handleSrpIdInvalid(_passwordCheckRequestId);
|
|
|
|
} else if (submitSaved) {
|
2018-07-10 19:41:11 +03:00
|
|
|
// Force reload and show form.
|
|
|
|
_password = PasswordSettings();
|
|
|
|
reloadPassword();
|
|
|
|
} else if (MTP::isFloodError(error)) {
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordError.fire(lang(lng_flood_error));
|
2018-08-10 22:19:46 +03:00
|
|
|
} else if (error.type() == qstr("PASSWORD_HASH_INVALID")
|
|
|
|
|| error.type() == qstr("SRP_PASSWORD_CHANGED")) {
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordError.fire(lang(lng_passport_password_wrong));
|
|
|
|
} else {
|
|
|
|
_passwordError.fire_copy(error.type());
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-08-10 22:19:46 +03:00
|
|
|
bool FormController::handleSrpIdInvalid(mtpRequestId &guard) {
|
|
|
|
const auto now = getms(true);
|
|
|
|
if (_lastSrpIdInvalidTime > 0
|
|
|
|
&& now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) {
|
|
|
|
_password.request.id = 0;
|
|
|
|
_passwordError.fire(Lang::Hard::ServerError());
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
_lastSrpIdInvalidTime = now;
|
|
|
|
requestPasswordData(guard);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::passwordServerError() {
|
|
|
|
_view->showCriticalError(Lang::Hard::ServerError());
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::checkSavedPasswordSettings(
|
|
|
|
const SavedCredentials &credentials) {
|
|
|
|
const auto callback = [=](const Core::CloudPasswordResult &check) {
|
|
|
|
checkSavedPasswordSettings(check, credentials);
|
|
|
|
};
|
|
|
|
checkPasswordHash(
|
|
|
|
_passwordCheckRequestId,
|
|
|
|
credentials.hashForAuth,
|
|
|
|
callback);
|
|
|
|
}
|
|
|
|
|
2018-07-10 19:41:11 +03:00
|
|
|
void FormController::checkSavedPasswordSettings(
|
2018-08-10 22:19:46 +03:00
|
|
|
const Core::CloudPasswordResult &check,
|
2018-07-10 19:41:11 +03:00
|
|
|
const SavedCredentials &credentials) {
|
|
|
|
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
2018-08-10 22:19:46 +03:00
|
|
|
check.result
|
2018-07-10 19:41:11 +03:00
|
|
|
)).done([=](const MTPaccount_PasswordSettings &result) {
|
|
|
|
Expects(result.type() == mtpc_account_passwordSettings);
|
|
|
|
|
|
|
|
_passwordCheckRequestId = 0;
|
|
|
|
const auto &data = result.c_account_passwordSettings();
|
2018-08-04 00:48:00 +03:00
|
|
|
if (data.has_secure_settings()) {
|
|
|
|
const auto &wrapped = data.vsecure_settings;
|
|
|
|
const auto &settings = wrapped.c_secureSecretSettings();
|
|
|
|
const auto algo = Core::ParseSecureSecretAlgo(
|
|
|
|
settings.vsecure_algo);
|
|
|
|
if (!algo) {
|
|
|
|
_view->showUpdateAppBox();
|
|
|
|
return;
|
|
|
|
} else if (!settings.vsecure_secret.v.isEmpty()
|
|
|
|
&& settings.vsecure_secret_id.v == credentials.secretId) {
|
|
|
|
_password.confirmedEmail = qs(data.vemail);
|
|
|
|
validateSecureSecret(
|
|
|
|
bytes::make_span(settings.vsecure_secret.v),
|
|
|
|
credentials.hashForSecret,
|
|
|
|
{},
|
|
|
|
settings.vsecure_secret_id.v);
|
|
|
|
}
|
2018-07-10 19:41:11 +03:00
|
|
|
}
|
|
|
|
if (_secret.empty()) {
|
|
|
|
Auth().data().forgetPassportCredentials();
|
|
|
|
showForm();
|
|
|
|
}
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_passwordCheckRequestId = 0;
|
2018-08-10 22:19:46 +03:00
|
|
|
if (error.type() != qstr("SRP_ID_INVALID")
|
|
|
|
|| !handleSrpIdInvalid(_passwordCheckRequestId)) {
|
|
|
|
} else {
|
|
|
|
Auth().data().forgetPassportCredentials();
|
|
|
|
showForm();
|
|
|
|
}
|
2018-07-10 19:41:11 +03:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-18 12:42:15 +04:00
|
|
|
void FormController::recoverPassword() {
|
|
|
|
if (!_password.hasRecovery) {
|
|
|
|
_view->show(Box<InformBox>(lang(lng_signin_no_email_forgot)));
|
|
|
|
return;
|
|
|
|
} else if (_recoverRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_recoverRequestId = request(MTPauth_RequestPasswordRecovery(
|
|
|
|
)).done([=](const MTPauth_PasswordRecovery &result) {
|
|
|
|
Expects(result.type() == mtpc_auth_passwordRecovery);
|
|
|
|
|
|
|
|
_recoverRequestId = 0;
|
|
|
|
|
|
|
|
const auto &data = result.c_auth_passwordRecovery();
|
|
|
|
const auto pattern = qs(data.vemail_pattern);
|
2018-04-20 20:42:51 +04:00
|
|
|
const auto box = _view->show(Box<RecoverBox>(
|
|
|
|
pattern,
|
|
|
|
_password.notEmptyPassport));
|
2018-07-10 19:41:11 +03:00
|
|
|
|
|
|
|
box->passwordCleared(
|
|
|
|
) | rpl::start_with_next([=] {
|
2018-04-18 12:42:15 +04:00
|
|
|
reloadPassword();
|
2018-07-10 19:41:11 +03:00
|
|
|
}, box->lifetime());
|
|
|
|
|
|
|
|
box->recoveryExpired(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
box->closeBox();
|
|
|
|
}, box->lifetime());
|
2018-04-18 12:42:15 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_recoverRequestId = 0;
|
|
|
|
_view->show(Box<InformBox>(Lang::Hard::ServerError()
|
|
|
|
+ '\n'
|
|
|
|
+ error.type()));
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-16 21:02:40 +04:00
|
|
|
void FormController::reloadPassword() {
|
|
|
|
requestPassword();
|
|
|
|
}
|
|
|
|
|
2018-07-10 19:41:11 +03:00
|
|
|
void FormController::reloadAndSubmitPassword(const QByteArray &password) {
|
|
|
|
_savedPasswordValue = password;
|
|
|
|
requestPassword();
|
|
|
|
}
|
|
|
|
|
2018-04-16 21:02:40 +04:00
|
|
|
void FormController::cancelPassword() {
|
|
|
|
if (_passwordRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_passwordRequestId = request(MTPaccount_UpdatePasswordSettings(
|
2018-08-10 22:19:46 +03:00
|
|
|
MTP_inputCheckPasswordEmpty(),
|
2018-04-16 21:02:40 +04:00
|
|
|
MTP_account_passwordInputSettings(
|
|
|
|
MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
|
2018-08-04 00:48:00 +03:00
|
|
|
MTP_passwordKdfAlgoUnknown(), // new_algo
|
2018-04-16 21:02:40 +04:00
|
|
|
MTP_bytes(QByteArray()), // new_password_hash
|
|
|
|
MTP_string(QString()), // hint
|
|
|
|
MTP_string(QString()), // email
|
2018-08-04 00:48:00 +03:00
|
|
|
MTPSecureSecretSettings())
|
2018-04-16 21:02:40 +04:00
|
|
|
)).done([=](const MTPBool &result) {
|
|
|
|
_passwordRequestId = 0;
|
|
|
|
reloadPassword();
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_passwordRequestId = 0;
|
|
|
|
reloadPassword();
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
void FormController::validateSecureSecret(
|
|
|
|
bytes::const_span encryptedSecret,
|
2018-07-10 19:41:11 +03:00
|
|
|
bytes::const_span passwordHashForSecret,
|
|
|
|
bytes::const_span passwordBytes,
|
2018-04-13 22:14:14 +04:00
|
|
|
uint64 serverSecretId) {
|
2018-07-10 19:41:11 +03:00
|
|
|
Expects(!passwordBytes.empty() || !passwordHashForSecret.empty());
|
|
|
|
|
|
|
|
if (!passwordHashForSecret.empty() && !encryptedSecret.empty()) {
|
|
|
|
_secret = DecryptSecureSecret(
|
|
|
|
encryptedSecret,
|
|
|
|
passwordHashForSecret);
|
2018-03-27 17:00:13 +04:00
|
|
|
if (_secret.empty()) {
|
2018-03-29 18:44:34 +04:00
|
|
|
_secretId = 0;
|
2018-04-15 20:22:13 +04:00
|
|
|
LOG(("API Error: Failed to decrypt secure secret."));
|
2018-07-10 19:41:11 +03:00
|
|
|
if (!passwordBytes.empty()) {
|
|
|
|
suggestReset(bytes::make_vector(passwordBytes));
|
|
|
|
}
|
2018-04-15 20:22:13 +04:00
|
|
|
return;
|
2018-04-13 22:14:14 +04:00
|
|
|
} else if (CountSecureSecretId(_secret) != serverSecretId) {
|
|
|
|
_secret.clear();
|
|
|
|
_secretId = 0;
|
2018-04-15 20:22:13 +04:00
|
|
|
LOG(("API Error: Wrong secure secret id."));
|
2018-07-10 19:41:11 +03:00
|
|
|
if (!passwordBytes.empty()) {
|
|
|
|
suggestReset(bytes::make_vector(passwordBytes));
|
|
|
|
}
|
2018-04-15 20:22:13 +04:00
|
|
|
return;
|
2018-03-27 17:00:13 +04:00
|
|
|
} else {
|
2018-04-13 22:14:14 +04:00
|
|
|
_secretId = serverSecretId;
|
2018-03-29 18:44:34 +04:00
|
|
|
decryptValues();
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_secret.empty()) {
|
2018-07-10 19:41:11 +03:00
|
|
|
generateSecret(passwordBytes);
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
_secretReady.fire({});
|
|
|
|
}
|
|
|
|
|
2018-04-15 20:22:13 +04:00
|
|
|
void FormController::suggestReset(bytes::vector password) {
|
|
|
|
for (auto &[type, value] : _form.values) {
|
|
|
|
// if (!value.data.original.isEmpty()) {
|
|
|
|
resetValue(value);
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
_view->suggestReset([=] {
|
2018-08-10 22:19:46 +03:00
|
|
|
const auto callback = [=](const Core::CloudPasswordResult &check) {
|
|
|
|
resetSecret(check, password);
|
|
|
|
};
|
|
|
|
checkPasswordHash(
|
|
|
|
_saveSecretRequestId,
|
|
|
|
passwordHashForAuth(bytes::make_span(password)),
|
|
|
|
callback);
|
2018-04-15 20:22:13 +04:00
|
|
|
_secretReady.fire({});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-10 22:19:46 +03:00
|
|
|
void FormController::resetSecret(
|
|
|
|
const Core::CloudPasswordResult &check,
|
|
|
|
const bytes::vector &password) {
|
|
|
|
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
|
|
|
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
|
|
|
check.result,
|
|
|
|
MTP_account_passwordInputSettings(
|
|
|
|
MTP_flags(Flag::f_new_secure_settings),
|
|
|
|
MTPPasswordKdfAlgo(), // new_algo
|
|
|
|
MTPbytes(), // new_password_hash
|
|
|
|
MTPstring(), // hint
|
|
|
|
MTPstring(), // email
|
|
|
|
MTP_secureSecretSettings(
|
|
|
|
MTP_securePasswordKdfAlgoUnknown(), // secure_algo
|
|
|
|
MTP_bytes(QByteArray()), // secure_secret
|
|
|
|
MTP_long(0))) // secure_secret_id
|
|
|
|
)).done([=](const MTPBool &result) {
|
|
|
|
_saveSecretRequestId = 0;
|
|
|
|
generateSecret(password);
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_saveSecretRequestId = 0;
|
|
|
|
if (error.type() != qstr("SRP_ID_INVALID")
|
|
|
|
|| !handleSrpIdInvalid(_saveSecretRequestId)) {
|
|
|
|
formFail(error.type());
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::decryptValues() {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(!_secret.empty());
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
for (auto &[type, value] : _form.values) {
|
2018-03-29 18:44:34 +04:00
|
|
|
decryptValue(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
2018-04-17 21:54:52 +04:00
|
|
|
fillErrors();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::fillErrors() {
|
2018-04-17 23:42:53 +04:00
|
|
|
const auto find = [&](const MTPSecureValueType &type) -> Value* {
|
|
|
|
const auto converted = ConvertType(type);
|
|
|
|
const auto i = _form.values.find(ConvertType(type));
|
|
|
|
if (i != end(_form.values)) {
|
|
|
|
return &i->second;
|
|
|
|
}
|
|
|
|
LOG(("API Error: Value not found for error type."));
|
|
|
|
return nullptr;
|
|
|
|
};
|
2018-08-14 08:43:40 +03:00
|
|
|
using List = std::vector<File>;
|
|
|
|
const auto findScan = [&](List &list, bytes::const_span hash) -> File* {
|
|
|
|
const auto i = ranges::find_if(list, [&](const File &scan) {
|
2018-04-17 23:42:53 +04:00
|
|
|
return !bytes::compare(hash, scan.hash);
|
|
|
|
});
|
2018-08-14 08:43:40 +03:00
|
|
|
if (i != end(list)) {
|
2018-04-17 23:42:53 +04:00
|
|
|
return &*i;
|
|
|
|
}
|
2018-08-14 08:43:40 +03:00
|
|
|
return nullptr;
|
|
|
|
};
|
|
|
|
const auto scan = [&](Value &value, bytes::const_span hash) -> File* {
|
|
|
|
if (const auto translation = findScan(value.translations, hash)) {
|
|
|
|
return translation;
|
|
|
|
} else if (const auto scan = findScan(value.scans, hash)) {
|
|
|
|
return scan;
|
|
|
|
}
|
2018-04-17 23:42:53 +04:00
|
|
|
LOG(("API Error: File not found for error value."));
|
|
|
|
return nullptr;
|
|
|
|
};
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto setSpecialScanError = [&](SpecialFile type, auto &&data) {
|
|
|
|
if (const auto value = find(data.vtype)) {
|
2018-08-14 08:43:40 +03:00
|
|
|
if (value->requiresSpecialScan(type)) {
|
|
|
|
const auto i = value->specialScans.find(type);
|
|
|
|
if (i != value->specialScans.end()) {
|
|
|
|
i->second.error = qs(data.vtext);
|
|
|
|
} else {
|
|
|
|
LOG(("API Error: "
|
|
|
|
"Special scan %1 not found for error value."
|
|
|
|
).arg(int(type)));
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2018-04-17 23:42:53 +04:00
|
|
|
for (const auto &error : _form.pendingErrors) {
|
2018-08-12 22:48:43 +03:00
|
|
|
error.match([&](const MTPDsecureValueError &data) {
|
|
|
|
if (const auto value = find(data.vtype)) {
|
2018-08-14 14:37:03 +03:00
|
|
|
if (CanHaveErrors(value->type)) {
|
|
|
|
value->error = qs(data.vtext);
|
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
|
|
|
}, [&](const MTPDsecureValueErrorData &data) {
|
2018-04-17 23:42:53 +04:00
|
|
|
if (const auto value = find(data.vtype)) {
|
|
|
|
const auto key = qs(data.vfield);
|
2018-08-14 14:37:03 +03:00
|
|
|
if (CanHaveErrors(value->type)
|
|
|
|
&& !SkipFieldCheck(value, key)) {
|
2018-08-14 08:43:40 +03:00
|
|
|
value->data.parsed.fields[key].error = qs(data.vtext);
|
|
|
|
}
|
2018-04-17 21:54:52 +04:00
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}, [&](const MTPDsecureValueErrorFile &data) {
|
2018-04-17 23:42:53 +04:00
|
|
|
const auto hash = bytes::make_span(data.vfile_hash.v);
|
|
|
|
if (const auto value = find(data.vtype)) {
|
|
|
|
if (const auto file = scan(*value, hash)) {
|
|
|
|
file->error = qs(data.vtext);
|
2018-04-17 21:54:52 +04:00
|
|
|
}
|
2018-04-17 23:42:53 +04:00
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}, [&](const MTPDsecureValueErrorFiles &data) {
|
2018-04-17 23:42:53 +04:00
|
|
|
if (const auto value = find(data.vtype)) {
|
2018-08-14 14:37:03 +03:00
|
|
|
if (CanRequireScans(value->type)) {
|
|
|
|
value->scanMissingError = qs(data.vtext);
|
|
|
|
}
|
2018-04-17 23:42:53 +04:00
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}, [&](const MTPDsecureValueErrorTranslationFile &data) {
|
|
|
|
const auto hash = bytes::make_span(data.vfile_hash.v);
|
|
|
|
if (const auto value = find(data.vtype)) {
|
2018-08-14 08:43:40 +03:00
|
|
|
if (value->translationRequired) {
|
|
|
|
if (const auto file = scan(*value, hash)) {
|
|
|
|
file->error = qs(data.vtext);
|
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [&](const MTPDsecureValueErrorTranslationFiles &data) {
|
|
|
|
if (const auto value = find(data.vtype)) {
|
2018-08-14 08:43:40 +03:00
|
|
|
if (value->translationRequired) {
|
|
|
|
value->translationMissingError = qs(data.vtext);
|
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
|
|
|
}, [&](const MTPDsecureValueErrorFrontSide &data) {
|
2018-05-12 01:55:56 +03:00
|
|
|
setSpecialScanError(SpecialFile::FrontSide, data);
|
2018-08-12 22:48:43 +03:00
|
|
|
}, [&](const MTPDsecureValueErrorReverseSide &data) {
|
2018-05-12 01:55:56 +03:00
|
|
|
setSpecialScanError(SpecialFile::ReverseSide, data);
|
2018-08-12 22:48:43 +03:00
|
|
|
}, [&](const MTPDsecureValueErrorSelfie &data) {
|
2018-05-12 01:55:56 +03:00
|
|
|
setSpecialScanError(SpecialFile::Selfie, data);
|
2018-08-12 22:48:43 +03:00
|
|
|
});
|
2018-04-17 21:54:52 +04:00
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
void FormController::decryptValue(Value &value) const {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(!_secret.empty());
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
if (!validateValueSecrets(value)) {
|
|
|
|
resetValue(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-06 22:47:29 +04:00
|
|
|
if (!value.data.original.isEmpty()) {
|
2018-04-15 20:22:13 +04:00
|
|
|
const auto decrypted = DecryptData(
|
2018-04-06 22:47:29 +04:00
|
|
|
bytes::make_span(value.data.original),
|
|
|
|
value.data.hash,
|
2018-04-15 20:22:13 +04:00
|
|
|
value.data.secret);
|
|
|
|
if (decrypted.empty()) {
|
|
|
|
LOG(("API Error: Could not decrypt value fields."));
|
|
|
|
resetValue(value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto fields = DeserializeData(decrypted);
|
2018-04-15 19:06:53 +04:00
|
|
|
value.data.parsed.fields.clear();
|
|
|
|
for (const auto [key, text] : fields) {
|
|
|
|
value.data.parsed.fields[key] = { text };
|
|
|
|
}
|
2018-04-06 22:47:29 +04:00
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
bool FormController::validateValueSecrets(Value &value) const {
|
2018-04-06 22:47:29 +04:00
|
|
|
if (!value.data.original.isEmpty()) {
|
|
|
|
value.data.secret = DecryptValueSecret(
|
|
|
|
value.data.encryptedSecret,
|
|
|
|
_secret,
|
|
|
|
value.data.hash);
|
|
|
|
if (value.data.secret.empty()) {
|
2018-04-15 20:22:13 +04:00
|
|
|
LOG(("API Error: Could not decrypt data secret."));
|
2018-04-06 22:47:29 +04:00
|
|
|
return false;
|
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
const auto validateFileSecret = [&](File &file) {
|
2018-03-29 18:44:34 +04:00
|
|
|
file.secret = DecryptValueSecret(
|
|
|
|
file.encryptedSecret,
|
|
|
|
_secret,
|
|
|
|
file.hash);
|
|
|
|
if (file.secret.empty()) {
|
2018-04-15 20:22:13 +04:00
|
|
|
LOG(("API Error: Could not decrypt file secret."));
|
2018-03-29 18:44:34 +04:00
|
|
|
return false;
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
for (auto &scan : value.scans) {
|
|
|
|
if (!validateFileSecret(scan)) {
|
2018-04-10 23:00:52 +04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (auto &[type, file] : value.specialScans) {
|
|
|
|
if (!validateFileSecret(file)) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
void FormController::resetValue(Value &value) const {
|
|
|
|
value.fillDataFrom(Value(value.type));
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
rpl::producer<QString> FormController::passwordError() const {
|
|
|
|
return _passwordError.events();
|
|
|
|
}
|
|
|
|
|
2018-04-16 21:02:40 +04:00
|
|
|
const PasswordSettings &FormController::passwordSettings() const {
|
|
|
|
return _password;
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
void FormController::uploadScan(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
QByteArray &&content) {
|
2018-04-13 14:54:17 +04:00
|
|
|
if (!canAddScan(value)) {
|
|
|
|
_view->showToast(lang(lng_passport_scans_limit_reached));
|
|
|
|
return;
|
|
|
|
}
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto nonconst = findValue(value);
|
2018-04-12 19:45:04 +04:00
|
|
|
auto scanIndex = int(nonconst->scansInEdit.size());
|
|
|
|
nonconst->scansInEdit.emplace_back(
|
2018-04-03 22:24:31 +04:00
|
|
|
nonconst,
|
2018-03-29 00:40:42 +04:00
|
|
|
File(),
|
|
|
|
nullptr);
|
2018-04-12 19:45:04 +04:00
|
|
|
auto &scan = nonconst->scansInEdit.back();
|
|
|
|
encryptFile(scan, std::move(content), [=](UploadScanData &&result) {
|
|
|
|
Expects(scanIndex >= 0 && scanIndex < nonconst->scansInEdit.size());
|
2018-04-10 23:00:52 +04:00
|
|
|
|
|
|
|
uploadEncryptedFile(
|
2018-04-12 19:45:04 +04:00
|
|
|
nonconst->scansInEdit[scanIndex],
|
2018-04-10 23:00:52 +04:00
|
|
|
std::move(result));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::deleteScan(
|
|
|
|
not_null<const Value*> value,
|
2018-04-12 19:45:04 +04:00
|
|
|
int scanIndex) {
|
|
|
|
scanDeleteRestore(value, scanIndex, true);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::restoreScan(
|
|
|
|
not_null<const Value*> value,
|
2018-04-12 19:45:04 +04:00
|
|
|
int scanIndex) {
|
|
|
|
scanDeleteRestore(value, scanIndex, false);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
void FormController::uploadSpecialScan(
|
2018-04-10 23:00:52 +04:00
|
|
|
not_null<const Value*> value,
|
2018-05-12 01:55:56 +03:00
|
|
|
SpecialFile type,
|
2018-04-10 23:00:52 +04:00
|
|
|
QByteArray &&content) {
|
|
|
|
const auto nonconst = findValue(value);
|
2018-05-12 01:55:56 +03:00
|
|
|
auto scanInEdit = EditFile{ nonconst, File(), nullptr };
|
|
|
|
auto i = nonconst->specialScansInEdit.find(type);
|
|
|
|
if (i != nonconst->specialScansInEdit.end()) {
|
|
|
|
i->second = std::move(scanInEdit);
|
|
|
|
} else {
|
|
|
|
i = nonconst->specialScansInEdit.emplace(
|
|
|
|
type,
|
|
|
|
std::move(scanInEdit)).first;
|
|
|
|
}
|
|
|
|
auto &file = i->second;
|
2018-04-10 23:00:52 +04:00
|
|
|
encryptFile(file, std::move(content), [=](UploadScanData &&result) {
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto i = nonconst->specialScansInEdit.find(type);
|
|
|
|
Assert(i != nonconst->specialScansInEdit.end());
|
2018-04-10 23:00:52 +04:00
|
|
|
uploadEncryptedFile(
|
2018-05-12 01:55:56 +03:00
|
|
|
i->second,
|
2018-04-10 23:00:52 +04:00
|
|
|
std::move(result));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
void FormController::deleteSpecialScan(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
SpecialFile type) {
|
|
|
|
specialScanDeleteRestore(value, type, true);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
void FormController::restoreSpecialScan(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
SpecialFile type) {
|
|
|
|
specialScanDeleteRestore(value, type, false);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::prepareFile(
|
|
|
|
EditFile &file,
|
|
|
|
const QByteArray &content) {
|
|
|
|
const auto fileId = rand_value<uint64>();
|
2018-03-29 00:40:42 +04:00
|
|
|
file.fields.size = content.size();
|
|
|
|
file.fields.id = fileId;
|
|
|
|
file.fields.dcId = MTP::maindc();
|
2018-03-29 18:44:34 +04:00
|
|
|
file.fields.secret = GenerateSecretBytes();
|
|
|
|
file.fields.date = unixtime();
|
2018-03-29 00:40:42 +04:00
|
|
|
file.fields.image = ReadImage(bytes::make_span(content));
|
|
|
|
file.fields.downloadOffset = file.fields.size;
|
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(&file);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-04-10 23:00:52 +04:00
|
|
|
void FormController::encryptFile(
|
|
|
|
EditFile &file,
|
|
|
|
QByteArray &&content,
|
2018-06-04 18:35:11 +03:00
|
|
|
Fn<void(UploadScanData &&result)> callback) {
|
2018-04-10 23:00:52 +04:00
|
|
|
prepareFile(file, content);
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
const auto weak = std::weak_ptr<bool>(file.guard);
|
2018-03-25 15:37:57 +04:00
|
|
|
crl::async([
|
|
|
|
=,
|
2018-03-29 23:49:31 +04:00
|
|
|
fileId = file.fields.id,
|
2018-03-25 15:37:57 +04:00
|
|
|
bytes = std::move(content),
|
2018-03-29 23:49:31 +04:00
|
|
|
fileSecret = file.fields.secret
|
2018-03-25 15:37:57 +04:00
|
|
|
] {
|
|
|
|
auto data = EncryptData(
|
2018-03-27 16:16:00 +04:00
|
|
|
bytes::make_span(bytes),
|
2018-03-29 18:44:34 +04:00
|
|
|
fileSecret);
|
|
|
|
auto result = UploadScanData();
|
2018-03-29 00:40:42 +04:00
|
|
|
result.fileId = fileId;
|
2018-03-25 15:37:57 +04:00
|
|
|
result.hash = std::move(data.hash);
|
|
|
|
result.bytes = std::move(data.bytes);
|
|
|
|
result.md5checksum.resize(32);
|
|
|
|
hashMd5Hex(
|
|
|
|
result.bytes.data(),
|
|
|
|
result.bytes.size(),
|
|
|
|
result.md5checksum.data());
|
|
|
|
crl::on_main([=, encrypted = std::move(result)]() mutable {
|
2018-03-29 23:49:31 +04:00
|
|
|
if (weak.lock()) {
|
2018-04-10 23:00:52 +04:00
|
|
|
callback(std::move(encrypted));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-31 05:45:40 +04:00
|
|
|
void FormController::scanDeleteRestore(
|
2018-04-03 22:24:31 +04:00
|
|
|
not_null<const Value*> value,
|
2018-04-12 19:45:04 +04:00
|
|
|
int scanIndex,
|
2018-03-31 05:45:40 +04:00
|
|
|
bool deleted) {
|
2018-04-12 19:45:04 +04:00
|
|
|
Expects(scanIndex >= 0 && scanIndex < value->scansInEdit.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto nonconst = findValue(value);
|
2018-04-12 19:45:04 +04:00
|
|
|
auto &scan = nonconst->scansInEdit[scanIndex];
|
2018-04-13 14:54:17 +04:00
|
|
|
if (scan.deleted && !deleted) {
|
|
|
|
if (!canAddScan(value)) {
|
|
|
|
_view->showToast(lang(lng_passport_scans_limit_reached));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
scan.deleted = deleted;
|
|
|
|
_scanUpdated.fire(&scan);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
void FormController::specialScanDeleteRestore(
|
2018-04-10 23:00:52 +04:00
|
|
|
not_null<const Value*> value,
|
2018-05-12 01:55:56 +03:00
|
|
|
SpecialFile type,
|
2018-04-10 23:00:52 +04:00
|
|
|
bool deleted) {
|
|
|
|
const auto nonconst = findValue(value);
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto i = nonconst->specialScansInEdit.find(type);
|
|
|
|
Assert(i != nonconst->specialScansInEdit.end());
|
|
|
|
auto &scan = i->second;
|
2018-04-12 19:45:04 +04:00
|
|
|
scan.deleted = deleted;
|
|
|
|
_scanUpdated.fire(&scan);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
2018-04-13 14:54:17 +04:00
|
|
|
bool FormController::canAddScan(not_null<const Value*> value) const {
|
|
|
|
const auto scansCount = ranges::count_if(
|
|
|
|
value->scansInEdit,
|
|
|
|
[](const EditFile &scan) { return !scan.deleted; });
|
|
|
|
return (scansCount < kDocumentScansLimit);
|
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
void FormController::subscribeToUploader() {
|
|
|
|
if (_uploaderSubscriptions) {
|
|
|
|
return;
|
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
|
|
|
|
using namespace Storage;
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
Auth().uploader().secureReady(
|
2018-03-29 00:40:42 +04:00
|
|
|
) | rpl::start_with_next([=](const UploadSecureDone &data) {
|
|
|
|
scanUploadDone(data);
|
2018-03-25 15:37:57 +04:00
|
|
|
}, _uploaderSubscriptions);
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
Auth().uploader().secureProgress(
|
2018-03-29 00:40:42 +04:00
|
|
|
) | rpl::start_with_next([=](const UploadSecureProgress &data) {
|
|
|
|
scanUploadProgress(data);
|
2018-03-25 15:37:57 +04:00
|
|
|
}, _uploaderSubscriptions);
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
Auth().uploader().secureFailed(
|
|
|
|
) | rpl::start_with_next([=](const FullMsgId &fullId) {
|
2018-03-29 00:40:42 +04:00
|
|
|
scanUploadFail(fullId);
|
2018-03-25 15:37:57 +04:00
|
|
|
}, _uploaderSubscriptions);
|
|
|
|
}
|
|
|
|
|
2018-04-10 23:00:52 +04:00
|
|
|
void FormController::uploadEncryptedFile(
|
|
|
|
EditFile &file,
|
2018-03-29 18:44:34 +04:00
|
|
|
UploadScanData &&data) {
|
2018-03-25 15:37:57 +04:00
|
|
|
subscribeToUploader();
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
file.uploadData = std::make_unique<UploadScanData>(std::move(data));
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 01:01:28 +04:00
|
|
|
auto prepared = std::make_shared<FileLoadResult>(
|
2018-03-25 15:37:57 +04:00
|
|
|
TaskId(),
|
2018-03-29 18:44:34 +04:00
|
|
|
file.uploadData->fileId,
|
2018-03-25 15:37:57 +04:00
|
|
|
FileLoadTo(PeerId(0), false, MsgId(0)),
|
|
|
|
TextWithTags(),
|
|
|
|
std::shared_ptr<SendingAlbum>(nullptr));
|
2018-03-29 01:01:28 +04:00
|
|
|
prepared->type = SendMediaType::Secure;
|
|
|
|
prepared->content = QByteArray::fromRawData(
|
2018-03-29 18:44:34 +04:00
|
|
|
reinterpret_cast<char*>(file.uploadData->bytes.data()),
|
|
|
|
file.uploadData->bytes.size());
|
2018-03-29 01:01:28 +04:00
|
|
|
prepared->setFileData(prepared->content);
|
2018-03-29 18:44:34 +04:00
|
|
|
prepared->filemd5 = file.uploadData->md5checksum;
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
file.uploadData->fullId = FullMsgId(0, clientMsgId());
|
|
|
|
Auth().uploader().upload(file.uploadData->fullId, std::move(prepared));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::scanUploadDone(const Storage::UploadSecureDone &data) {
|
|
|
|
if (const auto file = findEditFile(data.fullId)) {
|
2018-03-29 18:44:34 +04:00
|
|
|
Assert(file->uploadData != nullptr);
|
|
|
|
Assert(file->uploadData->fileId == data.fileId);
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
file->uploadData->partsCount = data.partsCount;
|
|
|
|
file->fields.hash = std::move(file->uploadData->hash);
|
|
|
|
file->fields.encryptedSecret = EncryptValueSecret(
|
|
|
|
file->fields.secret,
|
|
|
|
_secret,
|
|
|
|
file->fields.hash);
|
|
|
|
file->uploadData->fullId = FullMsgId();
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(file);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::scanUploadProgress(
|
|
|
|
const Storage::UploadSecureProgress &data) {
|
|
|
|
if (const auto file = findEditFile(data.fullId)) {
|
2018-03-29 18:44:34 +04:00
|
|
|
Assert(file->uploadData != nullptr);
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
file->uploadData->offset = data.offset;
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(file);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::scanUploadFail(const FullMsgId &fullId) {
|
|
|
|
if (const auto file = findEditFile(fullId)) {
|
2018-03-29 18:44:34 +04:00
|
|
|
Assert(file->uploadData != nullptr);
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
file->uploadData->offset = -1;
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(file);
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
rpl::producer<> FormController::secretReadyEvents() const {
|
|
|
|
return _secretReady.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::defaultEmail() const {
|
2018-03-27 17:00:13 +04:00
|
|
|
return _password.confirmedEmail;
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::defaultPhoneNumber() const {
|
|
|
|
if (const auto self = App::self()) {
|
|
|
|
return self->phone();
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
auto FormController::scanUpdated() const
|
|
|
|
-> rpl::producer<not_null<const EditFile*>> {
|
2018-03-25 15:37:57 +04:00
|
|
|
return _scanUpdated.events();
|
|
|
|
}
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
auto FormController::valueSaveFinished() const
|
|
|
|
-> rpl::producer<not_null<const Value*>> {
|
|
|
|
return _valueSaveFinished.events();
|
2018-04-06 22:47:29 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
auto FormController::verificationNeeded() const
|
2018-04-09 21:56:07 +04:00
|
|
|
-> rpl::producer<not_null<const Value*>> {
|
2018-04-06 22:47:29 +04:00
|
|
|
return _verificationNeeded.events();
|
|
|
|
}
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
auto FormController::verificationUpdate() const
|
|
|
|
-> rpl::producer<not_null<const Value*>> {
|
|
|
|
return _verificationUpdate.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::verify(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
const QString &code) {
|
|
|
|
if (value->verification.requestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto nonconst = findValue(value);
|
|
|
|
const auto prepared = code.trimmed();
|
|
|
|
Assert(nonconst->verification.codeLength != 0);
|
|
|
|
verificationError(nonconst, QString());
|
|
|
|
if (nonconst->verification.codeLength > 0
|
|
|
|
&& nonconst->verification.codeLength != prepared.size()) {
|
|
|
|
verificationError(nonconst, lang(lng_signin_wrong_code));
|
|
|
|
return;
|
|
|
|
} else if (prepared.isEmpty()) {
|
|
|
|
verificationError(nonconst, lang(lng_signin_wrong_code));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nonconst->verification.requestId = [&] {
|
|
|
|
switch (nonconst->type) {
|
|
|
|
case Value::Type::Phone:
|
|
|
|
return request(MTPaccount_VerifyPhone(
|
|
|
|
MTP_string(getPhoneFromValue(nonconst)),
|
|
|
|
MTP_string(nonconst->verification.phoneCodeHash),
|
|
|
|
MTP_string(prepared)
|
|
|
|
)).done([=](const MTPBool &result) {
|
|
|
|
savePlainTextValue(nonconst);
|
|
|
|
clearValueVerification(nonconst);
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
nonconst->verification.requestId = 0;
|
|
|
|
if (error.type() == qstr("PHONE_CODE_INVALID")) {
|
2018-04-12 14:20:54 +04:00
|
|
|
verificationError(
|
|
|
|
nonconst,
|
|
|
|
lang(lng_signin_wrong_code));
|
2018-04-09 21:56:07 +04:00
|
|
|
} else {
|
|
|
|
verificationError(nonconst, error.type());
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
case Value::Type::Email:
|
|
|
|
return request(MTPaccount_VerifyEmail(
|
|
|
|
MTP_string(getEmailFromValue(nonconst)),
|
|
|
|
MTP_string(prepared)
|
|
|
|
)).done([=](const MTPBool &result) {
|
|
|
|
savePlainTextValue(nonconst);
|
|
|
|
clearValueVerification(nonconst);
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
nonconst->verification.requestId = 0;
|
|
|
|
if (error.type() == qstr("CODE_INVALID")) {
|
2018-04-12 14:20:54 +04:00
|
|
|
verificationError(
|
|
|
|
nonconst,
|
|
|
|
lang(lng_signin_wrong_code));
|
2018-04-09 21:56:07 +04:00
|
|
|
} else {
|
|
|
|
verificationError(nonconst, error.type());
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
Unexpected("Type in FormController::verify().");
|
|
|
|
}();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::verificationError(
|
|
|
|
not_null<Value*> value,
|
|
|
|
const QString &text) {
|
|
|
|
value->verification.error = text;
|
|
|
|
_verificationUpdate.fire_copy(value);
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
const Form &FormController::form() const {
|
|
|
|
return _form;
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
not_null<Value*> FormController::findValue(not_null<const Value*> value) {
|
|
|
|
const auto i = _form.values.find(value->type);
|
|
|
|
Assert(i != end(_form.values));
|
|
|
|
const auto result = &i->second;
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
Ensures(result == value);
|
|
|
|
return result;
|
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
void FormController::startValueEdit(not_null<const Value*> value) {
|
2018-04-09 21:56:07 +04:00
|
|
|
const auto nonconst = findValue(value);
|
|
|
|
++nonconst->editScreens;
|
|
|
|
if (savingValue(nonconst)) {
|
2018-04-06 22:47:29 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
for (auto &scan : nonconst->scans) {
|
|
|
|
loadFile(scan);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
if (nonconst->translationRequired) {
|
|
|
|
for (auto &scan : nonconst->translations) {
|
|
|
|
loadFile(scan);
|
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (auto &[type, scan] : nonconst->specialScans) {
|
2018-08-12 22:48:43 +03:00
|
|
|
if (nonconst->requiresSpecialScan(type)) {
|
2018-05-12 01:55:56 +03:00
|
|
|
loadFile(scan);
|
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
nonconst->scansInEdit = ranges::view::all(
|
|
|
|
nonconst->scans
|
2018-04-03 22:24:31 +04:00
|
|
|
) | ranges::view::transform([=](const File &file) {
|
2018-04-09 21:56:07 +04:00
|
|
|
return EditFile(nonconst, file, nullptr);
|
2018-04-03 22:24:31 +04:00
|
|
|
}) | ranges::to_vector;
|
2018-04-10 23:00:52 +04:00
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
nonconst->translationsInEdit = ranges::view::all(
|
|
|
|
nonconst->translations
|
|
|
|
) | ranges::view::transform([=](const File &file) {
|
|
|
|
return EditFile(nonconst, file, nullptr);
|
|
|
|
}) | ranges::to_vector;
|
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
nonconst->specialScansInEdit.clear();
|
|
|
|
for (const auto &[type, scan] : nonconst->specialScans) {
|
|
|
|
nonconst->specialScansInEdit.emplace(type, EditFile(
|
2018-04-10 23:00:52 +04:00
|
|
|
nonconst,
|
2018-05-12 01:55:56 +03:00
|
|
|
scan,
|
|
|
|
nullptr));
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
|
|
|
|
2018-04-06 22:47:29 +04:00
|
|
|
nonconst->data.parsedInEdit = nonconst->data.parsed;
|
2018-03-21 08:35:32 +04:00
|
|
|
}
|
|
|
|
|
2018-04-10 23:00:52 +04:00
|
|
|
void FormController::loadFile(File &file) {
|
|
|
|
if (!file.image.isNull()) {
|
|
|
|
file.downloadOffset = file.size;
|
|
|
|
return;
|
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-04-10 23:00:52 +04:00
|
|
|
const auto key = FileKey{ file.id, file.dcId };
|
|
|
|
const auto i = _fileLoaders.find(key);
|
|
|
|
if (i != _fileLoaders.end()) {
|
|
|
|
return;
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
file.downloadOffset = 0;
|
|
|
|
const auto [j, ok] = _fileLoaders.emplace(
|
|
|
|
key,
|
|
|
|
std::make_unique<mtpFileLoader>(
|
|
|
|
file.dcId,
|
|
|
|
file.id,
|
|
|
|
file.accessHash,
|
|
|
|
0,
|
|
|
|
SecureFileLocation,
|
|
|
|
QString(),
|
|
|
|
file.size,
|
|
|
|
LoadToCacheAsWell,
|
|
|
|
LoadFromCloudOrLocal,
|
|
|
|
false));
|
|
|
|
const auto loader = j->second.get();
|
|
|
|
loader->connect(loader, &mtpFileLoader::progress, [=] {
|
|
|
|
if (loader->finished()) {
|
|
|
|
fileLoadDone(key, loader->bytes());
|
|
|
|
} else {
|
|
|
|
fileLoadProgress(key, loader->currentOffset());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
loader->connect(loader, &mtpFileLoader::failed, [=] {
|
|
|
|
fileLoadFail(key);
|
|
|
|
});
|
|
|
|
loader->start();
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) {
|
2018-03-29 18:44:34 +04:00
|
|
|
if (const auto [value, file] = findFile(key); file != nullptr) {
|
2018-03-25 15:37:57 +04:00
|
|
|
const auto decrypted = DecryptData(
|
2018-03-27 16:16:00 +04:00
|
|
|
bytes::make_span(bytes),
|
2018-03-29 18:44:34 +04:00
|
|
|
file->hash,
|
|
|
|
file->secret);
|
|
|
|
if (decrypted.empty()) {
|
|
|
|
fileLoadFail(key);
|
|
|
|
return;
|
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
file->downloadOffset = file->size;
|
|
|
|
file->image = App::readImage(QByteArray::fromRawData(
|
2018-03-25 15:37:57 +04:00
|
|
|
reinterpret_cast<const char*>(decrypted.data()),
|
|
|
|
decrypted.size()));
|
2018-03-29 00:40:42 +04:00
|
|
|
if (const auto fileInEdit = findEditFile(key)) {
|
|
|
|
fileInEdit->fields.image = file->image;
|
|
|
|
fileInEdit->fields.downloadOffset = file->downloadOffset;
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(fileInEdit);
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::fileLoadProgress(FileKey key, int offset) {
|
2018-03-29 18:44:34 +04:00
|
|
|
if (const auto [value, file] = findFile(key); file != nullptr) {
|
2018-03-29 00:40:42 +04:00
|
|
|
file->downloadOffset = offset;
|
|
|
|
if (const auto fileInEdit = findEditFile(key)) {
|
|
|
|
fileInEdit->fields.downloadOffset = file->downloadOffset;
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(fileInEdit);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::fileLoadFail(FileKey key) {
|
2018-03-29 18:44:34 +04:00
|
|
|
if (const auto [value, file] = findFile(key); file != nullptr) {
|
2018-03-29 00:40:42 +04:00
|
|
|
file->downloadOffset = -1;
|
|
|
|
if (const auto fileInEdit = findEditFile(key)) {
|
|
|
|
fileInEdit->fields.downloadOffset = file->downloadOffset;
|
2018-03-29 23:49:31 +04:00
|
|
|
_scanUpdated.fire(fileInEdit);
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
bool FormController::savingValue(not_null<const Value*> value) const {
|
|
|
|
return (value->saveRequestId != 0)
|
|
|
|
|| (value->verification.requestId != 0)
|
2018-04-17 19:41:52 +04:00
|
|
|
|| (value->verification.codeLength != 0)
|
|
|
|
|| uploadingScan(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormController::uploadingScan(not_null<const Value*> value) const {
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto uploading = [](const EditFile &file) {
|
|
|
|
return file.uploadData
|
|
|
|
&& file.uploadData->fullId
|
|
|
|
&& !file.deleted;
|
|
|
|
};
|
|
|
|
if (ranges::find_if(value->scansInEdit, uploading)
|
|
|
|
!= end(value->scansInEdit)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (ranges::find_if(value->specialScansInEdit, [&](const auto &pair) {
|
|
|
|
return uploading(pair.second);
|
|
|
|
}) != end(value->specialScansInEdit)) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-04-17 19:41:52 +04:00
|
|
|
for (const auto &scan : value->scansInEdit) {
|
2018-05-12 01:55:56 +03:00
|
|
|
if (uploading(scan)) {
|
2018-04-17 19:41:52 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (const auto &[type, scan] : value->specialScansInEdit) {
|
|
|
|
if (uploading(scan)) {
|
2018-04-17 19:41:52 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2018-04-09 21:56:07 +04:00
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
void FormController::cancelValueEdit(not_null<const Value*> value) {
|
2018-04-09 21:56:07 +04:00
|
|
|
Expects(value->editScreens > 0);
|
|
|
|
|
|
|
|
const auto nonconst = findValue(value);
|
|
|
|
--nonconst->editScreens;
|
|
|
|
clearValueEdit(nonconst);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::valueEditFailed(not_null<Value*> value) {
|
|
|
|
Expects(!savingValue(value));
|
|
|
|
|
|
|
|
if (value->editScreens == 0) {
|
|
|
|
clearValueEdit(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::clearValueEdit(not_null<Value*> value) {
|
|
|
|
if (savingValue(value)) {
|
2018-04-06 22:47:29 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-12 19:45:04 +04:00
|
|
|
value->scansInEdit.clear();
|
2018-05-12 01:55:56 +03:00
|
|
|
value->specialScansInEdit.clear();
|
2018-04-09 21:56:07 +04:00
|
|
|
value->data.encryptedSecretInEdit.clear();
|
|
|
|
value->data.hashInEdit.clear();
|
|
|
|
value->data.parsedInEdit = ValueMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::cancelValueVerification(not_null<const Value*> value) {
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto nonconst = findValue(value);
|
2018-04-09 21:56:07 +04:00
|
|
|
clearValueVerification(nonconst);
|
|
|
|
if (!savingValue(nonconst)) {
|
|
|
|
valueEditFailed(nonconst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::clearValueVerification(not_null<Value*> value) {
|
|
|
|
const auto was = (value->verification.codeLength != 0);
|
|
|
|
if (const auto requestId = base::take(value->verification.requestId)) {
|
|
|
|
request(requestId).cancel();
|
|
|
|
}
|
|
|
|
value->verification = Verification();
|
|
|
|
if (was) {
|
|
|
|
_verificationUpdate.fire_copy(value);
|
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
}
|
|
|
|
|
2018-03-29 23:49:31 +04:00
|
|
|
bool FormController::isEncryptedValue(Value::Type type) const {
|
2018-04-03 22:24:31 +04:00
|
|
|
return (type != Value::Type::Phone && type != Value::Type::Email);
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
bool FormController::editFileChanged(const EditFile &file) const {
|
|
|
|
if (file.uploadData) {
|
|
|
|
return !file.deleted;
|
|
|
|
}
|
|
|
|
return file.deleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormController::editValueChanged(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
const ValueMap &data) const {
|
|
|
|
auto filesCount = 0;
|
2018-04-12 19:45:04 +04:00
|
|
|
for (const auto &scan : value->scansInEdit) {
|
|
|
|
if (editFileChanged(scan)) {
|
2018-04-09 21:56:07 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (const auto &[type, scan] : value->specialScansInEdit) {
|
|
|
|
if (editFileChanged(scan)) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-04-09 21:56:07 +04:00
|
|
|
}
|
|
|
|
auto existing = value->data.parsed.fields;
|
|
|
|
for (const auto &[key, value] : data.fields) {
|
|
|
|
const auto i = existing.find(key);
|
|
|
|
if (i != existing.end()) {
|
2018-04-15 19:06:53 +04:00
|
|
|
if (i->second.text != value.text) {
|
2018-04-09 21:56:07 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
existing.erase(i);
|
2018-04-15 19:06:53 +04:00
|
|
|
} else if (!value.text.isEmpty()) {
|
2018-04-09 21:56:07 +04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return !existing.empty();
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
void FormController::saveValueEdit(
|
|
|
|
not_null<const Value*> value,
|
|
|
|
ValueMap &&data) {
|
2018-04-13 21:42:28 +04:00
|
|
|
if (savingValue(value) || _submitRequestId) {
|
2018-04-06 22:47:29 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-09 21:56:07 +04:00
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
// If we didn't change anything, we don't send save request
|
|
|
|
// and we don't reset value->error/[scan|translation]MissingError.
|
|
|
|
// Otherwise we reset them after save by re-parsing the value.
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto nonconst = findValue(value);
|
2018-04-09 21:56:07 +04:00
|
|
|
if (!editValueChanged(nonconst, data)) {
|
2018-04-10 23:34:43 +04:00
|
|
|
nonconst->saveRequestId = -1;
|
|
|
|
crl::on_main(this, [=] {
|
2018-04-12 19:45:04 +04:00
|
|
|
base::take(nonconst->scansInEdit);
|
2018-05-12 01:55:56 +03:00
|
|
|
base::take(nonconst->specialScansInEdit);
|
2018-04-10 23:34:43 +04:00
|
|
|
base::take(nonconst->data.encryptedSecretInEdit);
|
|
|
|
base::take(nonconst->data.hashInEdit);
|
|
|
|
base::take(nonconst->data.parsedInEdit);
|
|
|
|
nonconst->saveRequestId = 0;
|
|
|
|
_valueSaveFinished.fire_copy(nonconst);
|
|
|
|
});
|
2018-04-09 21:56:07 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-06 22:47:29 +04:00
|
|
|
nonconst->data.parsedInEdit = std::move(data);
|
2018-03-21 08:35:32 +04:00
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
if (isEncryptedValue(nonconst->type)) {
|
|
|
|
saveEncryptedValue(nonconst);
|
2018-03-29 23:49:31 +04:00
|
|
|
} else {
|
2018-04-03 22:24:31 +04:00
|
|
|
savePlainTextValue(nonconst);
|
2018-03-29 23:49:31 +04:00
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
}
|
|
|
|
|
2018-04-13 20:43:17 +04:00
|
|
|
void FormController::deleteValueEdit(not_null<const Value*> value) {
|
2018-04-13 21:42:28 +04:00
|
|
|
if (savingValue(value) || _submitRequestId) {
|
2018-04-13 20:43:17 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto nonconst = findValue(value);
|
|
|
|
nonconst->saveRequestId = request(MTPaccount_DeleteSecureValue(
|
|
|
|
MTP_vector<MTPSecureValueType>(1, ConvertType(nonconst->type))
|
|
|
|
)).done([=](const MTPBool &result) {
|
2018-08-14 14:37:03 +03:00
|
|
|
resetValue(*nonconst);
|
2018-04-13 20:43:17 +04:00
|
|
|
_valueSaveFinished.fire_copy(value);
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
nonconst->saveRequestId = 0;
|
2018-04-15 19:06:53 +04:00
|
|
|
valueSaveShowError(nonconst, error);
|
2018-04-13 20:43:17 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
void FormController::saveEncryptedValue(not_null<Value*> value) {
|
|
|
|
Expects(isEncryptedValue(value->type));
|
2018-03-21 08:35:32 +04:00
|
|
|
|
|
|
|
if (_secret.empty()) {
|
2018-03-27 17:00:13 +04:00
|
|
|
_secretCallbacks.push_back([=] {
|
2018-04-03 22:24:31 +04:00
|
|
|
saveEncryptedValue(value);
|
2018-03-21 08:35:32 +04:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
const auto wrapFile = [](const EditFile &file) {
|
2018-04-03 22:24:31 +04:00
|
|
|
if (const auto uploadData = file.uploadData.get()) {
|
|
|
|
return MTP_inputSecureFileUploaded(
|
2018-03-25 15:37:57 +04:00
|
|
|
MTP_long(file.fields.id),
|
2018-03-29 18:44:34 +04:00
|
|
|
MTP_int(uploadData->partsCount),
|
|
|
|
MTP_bytes(uploadData->md5checksum),
|
|
|
|
MTP_bytes(file.fields.hash),
|
2018-04-03 22:24:31 +04:00
|
|
|
MTP_bytes(file.fields.encryptedSecret));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
2018-04-03 22:24:31 +04:00
|
|
|
return MTP_inputSecureFile(
|
|
|
|
MTP_long(file.fields.id),
|
|
|
|
MTP_long(file.fields.accessHash));
|
|
|
|
};
|
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
auto files = QVector<MTPInputSecureFile>();
|
|
|
|
files.reserve(value->scansInEdit.size());
|
2018-04-12 19:45:04 +04:00
|
|
|
for (const auto &scan : value->scansInEdit) {
|
|
|
|
if (scan.deleted) {
|
2018-04-03 22:24:31 +04:00
|
|
|
continue;
|
|
|
|
}
|
2018-08-12 22:48:43 +03:00
|
|
|
files.push_back(wrapFile(scan));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto translations = QVector<MTPInputSecureFile>();
|
|
|
|
translations.reserve(value->translationsInEdit.size());
|
|
|
|
for (const auto &scan : value->translationsInEdit) {
|
|
|
|
if (scan.deleted) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
translations.push_back(wrapFile(scan));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
if (value->data.secret.empty()) {
|
|
|
|
value->data.secret = GenerateSecretBytes();
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
const auto encryptedData = EncryptData(
|
2018-04-15 19:06:53 +04:00
|
|
|
SerializeData(GetTexts(value->data.parsedInEdit)),
|
2018-04-03 22:24:31 +04:00
|
|
|
value->data.secret);
|
2018-04-06 22:47:29 +04:00
|
|
|
value->data.hashInEdit = encryptedData.hash;
|
|
|
|
value->data.encryptedSecretInEdit = EncryptValueSecret(
|
2018-04-03 22:24:31 +04:00
|
|
|
value->data.secret,
|
2018-03-29 18:44:34 +04:00
|
|
|
_secret,
|
2018-04-06 22:47:29 +04:00
|
|
|
value->data.hashInEdit);
|
2018-04-03 22:24:31 +04:00
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto hasSpecialFile = [&](SpecialFile type) {
|
|
|
|
const auto i = value->specialScansInEdit.find(type);
|
|
|
|
return (i != end(value->specialScansInEdit) && !i->second.deleted);
|
|
|
|
};
|
|
|
|
const auto specialFile = [&](SpecialFile type) {
|
|
|
|
const auto i = value->specialScansInEdit.find(type);
|
|
|
|
return (i != end(value->specialScansInEdit) && !i->second.deleted)
|
2018-08-12 22:48:43 +03:00
|
|
|
? wrapFile(i->second)
|
2018-05-12 01:55:56 +03:00
|
|
|
: MTPInputSecureFile();
|
|
|
|
};
|
|
|
|
const auto frontSide = specialFile(SpecialFile::FrontSide);
|
|
|
|
const auto reverseSide = specialFile(SpecialFile::ReverseSide);
|
|
|
|
const auto selfie = specialFile(SpecialFile::Selfie);
|
2018-04-03 22:24:31 +04:00
|
|
|
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto type = ConvertType(value->type);
|
2018-04-18 22:25:58 +04:00
|
|
|
const auto flags = (value->data.parsedInEdit.fields.empty()
|
2018-04-03 22:24:31 +04:00
|
|
|
? MTPDinputSecureValue::Flag(0)
|
|
|
|
: MTPDinputSecureValue::Flag::f_data)
|
2018-05-12 01:55:56 +03:00
|
|
|
| (hasSpecialFile(SpecialFile::FrontSide)
|
|
|
|
? MTPDinputSecureValue::Flag::f_front_side
|
|
|
|
: MTPDinputSecureValue::Flag(0))
|
|
|
|
| (hasSpecialFile(SpecialFile::ReverseSide)
|
|
|
|
? MTPDinputSecureValue::Flag::f_reverse_side
|
|
|
|
: MTPDinputSecureValue::Flag(0))
|
|
|
|
| (hasSpecialFile(SpecialFile::Selfie)
|
|
|
|
? MTPDinputSecureValue::Flag::f_selfie
|
|
|
|
: MTPDinputSecureValue::Flag(0))
|
2018-08-12 22:48:43 +03:00
|
|
|
| (value->translationsInEdit.empty()
|
|
|
|
? MTPDinputSecureValue::Flag(0)
|
|
|
|
: MTPDinputSecureValue::Flag::f_translation)
|
2018-04-12 19:45:04 +04:00
|
|
|
| (value->scansInEdit.empty()
|
2018-04-03 22:24:31 +04:00
|
|
|
? MTPDinputSecureValue::Flag(0)
|
2018-05-12 01:55:56 +03:00
|
|
|
: MTPDinputSecureValue::Flag::f_files);
|
2018-04-06 22:47:29 +04:00
|
|
|
Assert(flags != MTPDinputSecureValue::Flags(0));
|
|
|
|
|
|
|
|
sendSaveRequest(value, MTP_inputSecureValue(
|
|
|
|
MTP_flags(flags),
|
|
|
|
type,
|
|
|
|
MTP_secureData(
|
|
|
|
MTP_bytes(encryptedData.bytes),
|
|
|
|
MTP_bytes(value->data.hashInEdit),
|
|
|
|
MTP_bytes(value->data.encryptedSecretInEdit)),
|
2018-05-12 01:55:56 +03:00
|
|
|
frontSide,
|
|
|
|
reverseSide,
|
|
|
|
selfie,
|
2018-08-12 22:48:43 +03:00
|
|
|
MTP_vector<MTPInputSecureFile>(translations),
|
|
|
|
MTP_vector<MTPInputSecureFile>(files),
|
2018-05-12 01:55:56 +03:00
|
|
|
MTPSecurePlainData()));
|
2018-04-03 22:24:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::savePlainTextValue(not_null<Value*> value) {
|
|
|
|
Expects(!isEncryptedValue(value->type));
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
const auto text = getPlainTextFromValue(value);
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto type = [&] {
|
|
|
|
switch (value->type) {
|
|
|
|
case Value::Type::Phone: return MTP_secureValueTypePhone();
|
|
|
|
case Value::Type::Email: return MTP_secureValueTypeEmail();
|
2018-03-29 23:49:31 +04:00
|
|
|
}
|
|
|
|
Unexpected("Value type in savePlainTextValue().");
|
|
|
|
}();
|
2018-04-03 22:24:31 +04:00
|
|
|
const auto plain = [&] {
|
|
|
|
switch (value->type) {
|
|
|
|
case Value::Type::Phone: return MTP_securePlainPhone;
|
|
|
|
case Value::Type::Email: return MTP_securePlainEmail;
|
|
|
|
}
|
|
|
|
Unexpected("Value type in savePlainTextValue().");
|
|
|
|
}();
|
|
|
|
sendSaveRequest(value, MTP_inputSecureValue(
|
|
|
|
MTP_flags(MTPDinputSecureValue::Flag::f_plain_data),
|
|
|
|
type,
|
|
|
|
MTPSecureData(),
|
2018-05-12 01:55:56 +03:00
|
|
|
MTPInputSecureFile(),
|
|
|
|
MTPInputSecureFile(),
|
|
|
|
MTPInputSecureFile(),
|
2018-04-03 22:24:31 +04:00
|
|
|
MTPVector<MTPInputSecureFile>(),
|
2018-08-12 22:48:43 +03:00
|
|
|
MTPVector<MTPInputSecureFile>(),
|
2018-05-12 01:55:56 +03:00
|
|
|
plain(MTP_string(text))));
|
2018-03-29 23:49:31 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::sendSaveRequest(
|
2018-04-03 22:24:31 +04:00
|
|
|
not_null<Value*> value,
|
|
|
|
const MTPInputSecureValue &data) {
|
2018-04-06 22:47:29 +04:00
|
|
|
Expects(value->saveRequestId == 0);
|
|
|
|
|
|
|
|
value->saveRequestId = request(MTPaccount_SaveSecureValue(
|
2018-04-03 22:24:31 +04:00
|
|
|
data,
|
2018-03-29 23:49:31 +04:00
|
|
|
MTP_long(_secretId)
|
2018-04-03 22:24:31 +04:00
|
|
|
)).done([=](const MTPSecureValue &result) {
|
2018-05-12 01:55:56 +03:00
|
|
|
auto scansInEdit = base::take(value->scansInEdit);
|
|
|
|
for (auto &[type, scan] : base::take(value->specialScansInEdit)) {
|
|
|
|
scansInEdit.push_back(std::move(scan));
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-04-12 14:20:54 +04:00
|
|
|
|
2018-08-14 14:37:03 +03:00
|
|
|
auto refreshed = parseValue(result, scansInEdit);
|
|
|
|
decryptValue(refreshed);
|
|
|
|
value->fillDataFrom(std::move(refreshed));
|
2018-04-06 22:47:29 +04:00
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
_valueSaveFinished.fire_copy(value);
|
2018-03-25 15:37:57 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
2018-04-06 22:47:29 +04:00
|
|
|
value->saveRequestId = 0;
|
2018-04-15 19:06:53 +04:00
|
|
|
const auto code = error.type();
|
|
|
|
if (code == qstr("PHONE_VERIFICATION_NEEDED")) {
|
2018-04-06 22:47:29 +04:00
|
|
|
if (value->type == Value::Type::Phone) {
|
2018-04-09 21:56:07 +04:00
|
|
|
startPhoneVerification(value);
|
2018-04-06 22:47:29 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-15 19:06:53 +04:00
|
|
|
} else if (code == qstr("PHONE_NUMBER_INVALID")) {
|
|
|
|
if (value->type == Value::Type::Phone) {
|
|
|
|
value->data.parsedInEdit.fields["value"].error
|
|
|
|
= lang(lng_bad_phone);
|
|
|
|
valueSaveFailed(value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (code == qstr("EMAIL_VERIFICATION_NEEDED")) {
|
2018-04-06 22:47:29 +04:00
|
|
|
if (value->type == Value::Type::Email) {
|
2018-04-09 21:56:07 +04:00
|
|
|
startEmailVerification(value);
|
2018-04-06 22:47:29 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-04-15 19:06:53 +04:00
|
|
|
} else if (code == qstr("EMAIL_INVALID")) {
|
|
|
|
if (value->type == Value::Type::Email) {
|
|
|
|
value->data.parsedInEdit.fields["value"].error
|
|
|
|
= lang(lng_cloud_password_bad_email);
|
|
|
|
valueSaveFailed(value);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (SaveErrorRequiresRestart(code)) {
|
|
|
|
suggestRestart();
|
|
|
|
} else {
|
|
|
|
valueSaveShowError(value, error);
|
2018-04-06 22:47:29 +04:00
|
|
|
}
|
2018-04-09 21:56:07 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::getPhoneFromValue(
|
|
|
|
not_null<const Value*> value) const {
|
|
|
|
Expects(value->type == Value::Type::Phone);
|
|
|
|
|
|
|
|
return getPlainTextFromValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::getEmailFromValue(
|
|
|
|
not_null<const Value*> value) const {
|
|
|
|
Expects(value->type == Value::Type::Email);
|
|
|
|
|
|
|
|
return getPlainTextFromValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::getPlainTextFromValue(
|
|
|
|
not_null<const Value*> value) const {
|
|
|
|
Expects(value->type == Value::Type::Phone
|
|
|
|
|| value->type == Value::Type::Email);
|
|
|
|
|
|
|
|
const auto i = value->data.parsedInEdit.fields.find("value");
|
|
|
|
Assert(i != end(value->data.parsedInEdit.fields));
|
2018-04-15 19:06:53 +04:00
|
|
|
return i->second.text;
|
2018-04-09 21:56:07 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::startPhoneVerification(not_null<Value*> value) {
|
|
|
|
value->verification.requestId = request(MTPaccount_SendVerifyPhoneCode(
|
|
|
|
MTP_flags(MTPaccount_SendVerifyPhoneCode::Flag(0)),
|
|
|
|
MTP_string(getPhoneFromValue(value)),
|
|
|
|
MTPBool()
|
|
|
|
)).done([=](const MTPauth_SentCode &result) {
|
|
|
|
Expects(result.type() == mtpc_auth_sentCode);
|
|
|
|
|
|
|
|
value->verification.requestId = 0;
|
|
|
|
|
|
|
|
const auto &data = result.c_auth_sentCode();
|
|
|
|
value->verification.phoneCodeHash = qs(data.vphone_code_hash);
|
|
|
|
switch (data.vtype.type()) {
|
|
|
|
case mtpc_auth_sentCodeTypeApp:
|
|
|
|
LOG(("API Error: sentCodeTypeApp not expected "
|
|
|
|
"in FormController::startPhoneVerification."));
|
|
|
|
return;
|
|
|
|
case mtpc_auth_sentCodeTypeFlashCall:
|
|
|
|
LOG(("API Error: sentCodeTypeFlashCall not expected "
|
|
|
|
"in FormController::startPhoneVerification."));
|
|
|
|
return;
|
|
|
|
case mtpc_auth_sentCodeTypeCall: {
|
|
|
|
const auto &type = data.vtype.c_auth_sentCodeTypeCall();
|
|
|
|
value->verification.codeLength = (type.vlength.v > 0)
|
|
|
|
? type.vlength.v
|
|
|
|
: -1;
|
|
|
|
value->verification.call = std::make_unique<SentCodeCall>(
|
|
|
|
[=] { requestPhoneCall(value); },
|
|
|
|
[=] { _verificationUpdate.fire_copy(value); });
|
|
|
|
value->verification.call->setStatus(
|
|
|
|
{ SentCodeCall::State::Called, 0 });
|
|
|
|
if (data.has_next_type()) {
|
|
|
|
LOG(("API Error: next_type is not supported for calls."));
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case mtpc_auth_sentCodeTypeSms: {
|
|
|
|
const auto &type = data.vtype.c_auth_sentCodeTypeSms();
|
|
|
|
value->verification.codeLength = (type.vlength.v > 0)
|
|
|
|
? type.vlength.v
|
|
|
|
: -1;
|
|
|
|
const auto &next = data.vnext_type;
|
|
|
|
if (data.has_next_type()
|
|
|
|
&& next.type() == mtpc_auth_codeTypeCall) {
|
|
|
|
value->verification.call = std::make_unique<SentCodeCall>(
|
|
|
|
[=] { requestPhoneCall(value); },
|
|
|
|
[=] { _verificationUpdate.fire_copy(value); });
|
|
|
|
value->verification.call->setStatus({
|
|
|
|
SentCodeCall::State::Waiting,
|
|
|
|
data.has_timeout() ? data.vtimeout.v : 60 });
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
_verificationNeeded.fire_copy(value);
|
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
value->verification.requestId = 0;
|
2018-04-15 19:06:53 +04:00
|
|
|
valueSaveShowError(value, error);
|
2018-04-09 21:56:07 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::startEmailVerification(not_null<Value*> value) {
|
|
|
|
value->verification.requestId = request(MTPaccount_SendVerifyEmailCode(
|
|
|
|
MTP_string(getEmailFromValue(value))
|
|
|
|
)).done([=](const MTPaccount_SentEmailCode &result) {
|
|
|
|
Expects(result.type() == mtpc_account_sentEmailCode);
|
|
|
|
|
|
|
|
value->verification.requestId = 0;
|
|
|
|
const auto &data = result.c_account_sentEmailCode();
|
|
|
|
value->verification.codeLength = (data.vlength.v > 0)
|
|
|
|
? data.vlength.v
|
|
|
|
: -1;
|
|
|
|
_verificationNeeded.fire_copy(value);
|
|
|
|
}).fail([=](const RPCError &error) {
|
2018-04-15 19:06:53 +04:00
|
|
|
valueSaveShowError(value, error);
|
2018-03-21 08:35:32 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-09 21:56:07 +04:00
|
|
|
|
|
|
|
void FormController::requestPhoneCall(not_null<Value*> value) {
|
|
|
|
Expects(value->verification.call != nullptr);
|
|
|
|
|
|
|
|
value->verification.call->setStatus(
|
|
|
|
{ SentCodeCall::State::Calling, 0 });
|
|
|
|
request(MTPauth_ResendCode(
|
|
|
|
MTP_string(getPhoneFromValue(value)),
|
|
|
|
MTP_string(value->verification.phoneCodeHash)
|
|
|
|
)).done([=](const MTPauth_SentCode &code) {
|
|
|
|
value->verification.call->callDone();
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
void FormController::valueSaveShowError(
|
2018-04-09 21:56:07 +04:00
|
|
|
not_null<Value*> value,
|
|
|
|
const RPCError &error) {
|
2018-04-15 19:06:53 +04:00
|
|
|
_view->show(Box<InformBox>(
|
|
|
|
Lang::Hard::SecureSaveError() + "\n" + error.type()));
|
|
|
|
valueSaveFailed(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::valueSaveFailed(not_null<Value*> value) {
|
2018-04-09 21:56:07 +04:00
|
|
|
valueEditFailed(value);
|
|
|
|
_valueSaveFinished.fire_copy(value);
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
void FormController::generateSecret(bytes::const_span password) {
|
2018-07-10 19:41:11 +03:00
|
|
|
Expects(!password.empty());
|
|
|
|
|
2018-03-21 08:35:32 +04:00
|
|
|
if (_saveSecretRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto secret = GenerateSecretBytes();
|
2018-03-27 17:00:13 +04:00
|
|
|
|
2018-07-10 19:41:11 +03:00
|
|
|
auto saved = SavedCredentials();
|
2018-08-10 22:19:46 +03:00
|
|
|
saved.hashForAuth = _passwordCheckHash;
|
2018-08-04 00:48:00 +03:00
|
|
|
saved.hashForSecret = Core::ComputeSecureSecretHash(
|
|
|
|
_password.newSecureAlgo,
|
2018-03-29 18:44:34 +04:00
|
|
|
password);
|
2018-07-10 19:41:11 +03:00
|
|
|
saved.secretId = CountSecureSecretId(secret);
|
2018-03-29 18:44:34 +04:00
|
|
|
|
2018-08-10 22:19:46 +03:00
|
|
|
const auto callback = [=](const Core::CloudPasswordResult &check) {
|
|
|
|
saveSecret(check, saved, secret);
|
|
|
|
};
|
|
|
|
checkPasswordHash(_saveSecretRequestId, saved.hashForAuth, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::saveSecret(
|
|
|
|
const Core::CloudPasswordResult &check,
|
|
|
|
const SavedCredentials &saved,
|
|
|
|
const bytes::vector &secret) {
|
|
|
|
const auto encryptedSecret = EncryptSecureSecret(
|
2018-07-10 19:41:11 +03:00
|
|
|
secret,
|
|
|
|
saved.hashForSecret);
|
2018-03-27 17:00:13 +04:00
|
|
|
|
2018-03-21 08:35:32 +04:00
|
|
|
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
|
|
|
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
2018-08-10 22:19:46 +03:00
|
|
|
check.result,
|
2018-03-21 08:35:32 +04:00
|
|
|
MTP_account_passwordInputSettings(
|
2018-08-04 00:48:00 +03:00
|
|
|
MTP_flags(Flag::f_new_secure_settings),
|
|
|
|
MTPPasswordKdfAlgo(), // new_algo
|
2018-03-21 08:35:32 +04:00
|
|
|
MTPbytes(), // new_password_hash
|
|
|
|
MTPstring(), // hint
|
|
|
|
MTPstring(), // email
|
2018-08-04 00:48:00 +03:00
|
|
|
MTP_secureSecretSettings(
|
|
|
|
Core::PrepareSecureSecretAlgo(_password.newSecureAlgo),
|
|
|
|
MTP_bytes(encryptedSecret),
|
|
|
|
MTP_long(saved.secretId)))
|
2018-03-21 08:35:32 +04:00
|
|
|
)).done([=](const MTPBool &result) {
|
2018-07-10 19:41:11 +03:00
|
|
|
Auth().data().rememberPassportCredentials(
|
|
|
|
std::move(saved),
|
|
|
|
kRememberCredentialsDelay);
|
|
|
|
|
2018-03-21 08:35:32 +04:00
|
|
|
_saveSecretRequestId = 0;
|
|
|
|
_secret = secret;
|
2018-07-10 19:41:11 +03:00
|
|
|
_secretId = saved.secretId;
|
2018-03-27 17:00:13 +04:00
|
|
|
//_password.salt = newPasswordSaltFull;
|
2018-03-25 15:37:57 +04:00
|
|
|
for (const auto &callback : base::take(_secretCallbacks)) {
|
|
|
|
callback();
|
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_saveSecretRequestId = 0;
|
2018-08-10 22:19:46 +03:00
|
|
|
if (error.type() != qstr("SRP_ID_INVALID")
|
|
|
|
|| !handleSrpIdInvalid(_saveSecretRequestId)) {
|
|
|
|
suggestRestart();
|
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
void FormController::suggestRestart() {
|
|
|
|
_suggestingRestart = true;
|
|
|
|
_view->show(Box<ConfirmBox>(
|
|
|
|
lang(lng_passport_restart_sure),
|
|
|
|
lang(lng_passport_restart),
|
|
|
|
[=] { _controller->showPassportForm(_request); },
|
|
|
|
[=] { cancel(); }));
|
|
|
|
}
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
void FormController::requestForm() {
|
2018-04-12 19:45:04 +04:00
|
|
|
if (_request.payload.isEmpty()) {
|
|
|
|
_formRequestId = -1;
|
2018-04-15 19:06:53 +04:00
|
|
|
formFail("PAYLOAD_EMPTY");
|
2018-04-12 19:45:04 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-03-19 20:22:27 +04:00
|
|
|
_formRequestId = request(MTPaccount_GetAuthorizationForm(
|
2018-03-18 12:51:14 +04:00
|
|
|
MTP_int(_request.botId),
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_string(_request.scope),
|
2018-04-12 19:45:04 +04:00
|
|
|
MTP_string(_request.publicKey)
|
2018-03-18 12:51:14 +04:00
|
|
|
)).done([=](const MTPaccount_AuthorizationForm &result) {
|
2018-03-19 20:22:27 +04:00
|
|
|
_formRequestId = 0;
|
2018-03-18 12:51:14 +04:00
|
|
|
formDone(result);
|
|
|
|
}).fail([=](const RPCError &error) {
|
2018-04-15 19:06:53 +04:00
|
|
|
formFail(error.type());
|
2018-03-18 12:51:14 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
auto FormController::parseFiles(
|
|
|
|
const QVector<MTPSecureFile> &data,
|
|
|
|
const std::vector<EditFile> &editData) const
|
|
|
|
-> std::vector<File> {
|
|
|
|
auto result = std::vector<File>();
|
|
|
|
result.reserve(data.size());
|
|
|
|
|
|
|
|
for (const auto &file : data) {
|
2018-04-10 23:00:52 +04:00
|
|
|
if (auto normal = parseFile(file, editData)) {
|
|
|
|
result.push_back(std::move(*normal));
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-04-10 23:00:52 +04:00
|
|
|
auto FormController::parseFile(
|
|
|
|
const MTPSecureFile &data,
|
|
|
|
const std::vector<EditFile> &editData) const
|
|
|
|
-> base::optional<File> {
|
|
|
|
switch (data.type()) {
|
|
|
|
case mtpc_secureFileEmpty:
|
|
|
|
return base::none;
|
|
|
|
|
|
|
|
case mtpc_secureFile: {
|
|
|
|
const auto &fields = data.c_secureFile();
|
|
|
|
auto result = File();
|
|
|
|
result.id = fields.vid.v;
|
|
|
|
result.accessHash = fields.vaccess_hash.v;
|
|
|
|
result.size = fields.vsize.v;
|
|
|
|
result.date = fields.vdate.v;
|
|
|
|
result.dcId = fields.vdc_id.v;
|
|
|
|
result.hash = bytes::make_vector(fields.vfile_hash.v);
|
|
|
|
result.encryptedSecret = bytes::make_vector(fields.vsecret.v);
|
|
|
|
fillDownloadedFile(result, editData);
|
|
|
|
return result;
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
Unexpected("Type in FormController::parseFile.");
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::fillDownloadedFile(
|
|
|
|
File &destination,
|
|
|
|
const std::vector<EditFile> &source) const {
|
|
|
|
const auto i = ranges::find(
|
|
|
|
source,
|
|
|
|
destination.hash,
|
|
|
|
[](const EditFile &file) { return file.fields.hash; });
|
|
|
|
if (i == source.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
destination.image = i->fields.image;
|
|
|
|
destination.downloadOffset = i->fields.downloadOffset;
|
|
|
|
if (!i->uploadData) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Local::writeImage(
|
|
|
|
StorageKey(
|
|
|
|
storageMix32To64(
|
|
|
|
SecureFileLocation,
|
|
|
|
destination.dcId),
|
|
|
|
destination.id),
|
|
|
|
StorageImageSaved(QByteArray::fromRawData(
|
|
|
|
reinterpret_cast<const char*>(
|
|
|
|
i->uploadData->bytes.data()),
|
|
|
|
i->uploadData->bytes.size())));
|
|
|
|
}
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
auto FormController::parseValue(
|
2018-04-12 19:45:04 +04:00
|
|
|
const MTPSecureValue &value,
|
|
|
|
const std::vector<EditFile> &editData) const -> Value {
|
2018-04-03 22:24:31 +04:00
|
|
|
Expects(value.type() == mtpc_secureValue);
|
|
|
|
|
|
|
|
const auto &data = value.c_secureValue();
|
|
|
|
const auto type = ConvertType(data.vtype);
|
2018-03-29 18:44:34 +04:00
|
|
|
auto result = Value(type);
|
2018-04-12 19:45:04 +04:00
|
|
|
result.submitHash = bytes::make_vector(data.vhash.v);
|
2018-04-03 22:24:31 +04:00
|
|
|
if (data.has_data()) {
|
|
|
|
Assert(data.vdata.type() == mtpc_secureData);
|
|
|
|
const auto &fields = data.vdata.c_secureData();
|
|
|
|
result.data.original = fields.vdata.v;
|
|
|
|
result.data.hash = bytes::make_vector(fields.vdata_hash.v);
|
|
|
|
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
|
|
|
|
}
|
|
|
|
if (data.has_files()) {
|
2018-04-12 19:45:04 +04:00
|
|
|
result.scans = parseFiles(data.vfiles.v, editData);
|
2018-04-03 22:24:31 +04:00
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
const auto parseSpecialScan = [&](
|
|
|
|
SpecialFile type,
|
|
|
|
const MTPSecureFile &file) {
|
|
|
|
if (auto parsed = parseFile(file, editData)) {
|
|
|
|
result.specialScans.emplace(type, std::move(*parsed));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (data.has_front_side()) {
|
|
|
|
parseSpecialScan(SpecialFile::FrontSide, data.vfront_side);
|
|
|
|
}
|
|
|
|
if (data.has_reverse_side()) {
|
|
|
|
parseSpecialScan(SpecialFile::ReverseSide, data.vreverse_side);
|
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
if (data.has_selfie()) {
|
2018-05-12 01:55:56 +03:00
|
|
|
parseSpecialScan(SpecialFile::Selfie, data.vselfie);
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-04-03 22:24:31 +04:00
|
|
|
if (data.has_plain_data()) {
|
|
|
|
switch (data.vplain_data.type()) {
|
|
|
|
case mtpc_securePlainPhone: {
|
|
|
|
const auto &fields = data.vplain_data.c_securePlainPhone();
|
2018-04-15 19:06:53 +04:00
|
|
|
result.data.parsed.fields["value"].text = qs(fields.vphone);
|
2018-04-03 22:24:31 +04:00
|
|
|
} break;
|
|
|
|
case mtpc_securePlainEmail: {
|
|
|
|
const auto &fields = data.vplain_data.c_securePlainEmail();
|
2018-04-15 19:06:53 +04:00
|
|
|
result.data.parsed.fields["value"].text = qs(fields.vemail);
|
2018-04-03 22:24:31 +04:00
|
|
|
} break;
|
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
|
2018-04-10 23:00:52 +04:00
|
|
|
const auto found = [&](const EditFile &file) {
|
|
|
|
return (file.uploadData && file.uploadData->fullId == fullId);
|
|
|
|
};
|
2018-04-03 22:24:31 +04:00
|
|
|
for (auto &[type, value] : _form.values) {
|
2018-04-12 19:45:04 +04:00
|
|
|
for (auto &scan : value.scansInEdit) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return &scan;
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (auto &[special, scan] : value.specialScansInEdit) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return &scan;
|
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
auto FormController::findEditFile(const FileKey &key) -> EditFile* {
|
2018-04-10 23:00:52 +04:00
|
|
|
const auto found = [&](const EditFile &file) {
|
|
|
|
return (file.fields.dcId == key.dcId && file.fields.id == key.id);
|
|
|
|
};
|
2018-04-03 22:24:31 +04:00
|
|
|
for (auto &[type, value] : _form.values) {
|
2018-04-12 19:45:04 +04:00
|
|
|
for (auto &scan : value.scansInEdit) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return &scan;
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (auto &[special, scan] : value.specialScansInEdit) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return &scan;
|
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
auto FormController::findFile(const FileKey &key)
|
2018-03-29 18:44:34 +04:00
|
|
|
-> std::pair<Value*, File*> {
|
2018-04-10 23:00:52 +04:00
|
|
|
const auto found = [&](const File &file) {
|
|
|
|
return (file.dcId == key.dcId) && (file.id == key.id);
|
|
|
|
};
|
2018-04-03 22:24:31 +04:00
|
|
|
for (auto &[type, value] : _form.values) {
|
2018-04-12 19:45:04 +04:00
|
|
|
for (auto &scan : value.scans) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return { &value, &scan };
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
}
|
2018-05-12 01:55:56 +03:00
|
|
|
for (auto &[special, scan] : value.specialScans) {
|
|
|
|
if (found(scan)) {
|
|
|
|
return { &value, &scan };
|
|
|
|
}
|
2018-04-10 23:00:52 +04:00
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
return { nullptr, nullptr };
|
|
|
|
}
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
|
2018-08-12 22:48:43 +03:00
|
|
|
if (!parseForm(result)) {
|
|
|
|
_view->showCriticalError(lang(lng_passport_form_error));
|
|
|
|
} else if (!_passwordRequestId) {
|
2018-03-31 05:45:40 +04:00
|
|
|
showForm();
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-08-12 22:48:43 +03:00
|
|
|
bool FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
|
2018-03-18 12:51:14 +04:00
|
|
|
Expects(result.type() == mtpc_account_authorizationForm);
|
|
|
|
|
|
|
|
const auto &data = result.c_account_authorizationForm();
|
2018-03-27 17:00:13 +04:00
|
|
|
|
|
|
|
App::feedUsers(data.vusers);
|
|
|
|
|
2018-04-03 22:24:31 +04:00
|
|
|
for (const auto &value : data.vvalues.v) {
|
|
|
|
auto parsed = parseValue(value);
|
|
|
|
const auto type = parsed.type;
|
|
|
|
const auto alreadyIt = _form.values.find(type);
|
|
|
|
if (alreadyIt != _form.values.end()) {
|
|
|
|
LOG(("API Error: Two values for type %1 in authorization form"
|
|
|
|
"%1").arg(int(type)));
|
2018-08-12 22:48:43 +03:00
|
|
|
return false;
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
2018-04-03 22:24:31 +04:00
|
|
|
_form.values.emplace(type, std::move(parsed));
|
|
|
|
}
|
|
|
|
if (data.has_privacy_policy_url()) {
|
|
|
|
_form.privacyPolicyUrl = qs(data.vprivacy_policy_url);
|
|
|
|
}
|
|
|
|
for (const auto &required : data.vrequired_types.v) {
|
2018-08-12 22:48:43 +03:00
|
|
|
const auto row = CollectRequestedRow(required);
|
2018-08-14 14:37:03 +03:00
|
|
|
for (const auto requested : row.values) {
|
|
|
|
const auto type = requested.type;
|
|
|
|
const auto [i, ok] = _form.values.emplace(type, Value(type));
|
|
|
|
auto &value = i->second;
|
|
|
|
value.translationRequired = requested.translationRequired;
|
|
|
|
value.selfieRequired = requested.selfieRequired;
|
|
|
|
value.nativeNames = requested.nativeNames;
|
2018-08-12 22:48:43 +03:00
|
|
|
}
|
|
|
|
_form.request.push_back(row.values
|
|
|
|
| ranges::view::transform([](const RequestedValue &value) {
|
|
|
|
return value.type;
|
|
|
|
}) | ranges::to_vector);
|
|
|
|
}
|
|
|
|
if (!ValidateForm(_form)) {
|
|
|
|
return false;
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
_bot = App::userLoaded(_request.botId);
|
2018-04-17 23:42:53 +04:00
|
|
|
_form.pendingErrors = data.verrors.v;
|
2018-08-12 22:48:43 +03:00
|
|
|
return true;
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
void FormController::formFail(const QString &error) {
|
2018-07-10 19:41:11 +03:00
|
|
|
_savedPasswordValue = QByteArray();
|
2018-04-15 19:06:53 +04:00
|
|
|
_serviceErrorText = error;
|
2018-07-10 23:15:54 +03:00
|
|
|
if (error == "APP_VERSION_OUTDATED") {
|
|
|
|
_view->showUpdateAppBox();
|
|
|
|
} else {
|
|
|
|
_view->showCriticalError(
|
|
|
|
lang(lng_passport_form_error) + "\n" + error);
|
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::requestPassword() {
|
2018-04-16 21:02:40 +04:00
|
|
|
if (_passwordRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordRequestId = request(MTPaccount_GetPassword(
|
2018-03-18 12:51:14 +04:00
|
|
|
)).done([=](const MTPaccount_Password &result) {
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordRequestId = 0;
|
2018-03-18 12:51:14 +04:00
|
|
|
passwordDone(result);
|
2018-03-19 20:22:27 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
2018-04-15 19:06:53 +04:00
|
|
|
formFail(error.type());
|
2018-03-18 12:51:14 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::passwordDone(const MTPaccount_Password &result) {
|
2018-08-04 00:48:00 +03:00
|
|
|
Expects(result.type() == mtpc_account_password);
|
|
|
|
|
|
|
|
const auto changed = applyPassword(result.c_account_password());
|
2018-04-17 15:26:11 +04:00
|
|
|
if (changed && !_formRequestId) {
|
2018-03-31 05:45:40 +04:00
|
|
|
showForm();
|
|
|
|
}
|
2018-04-17 15:26:11 +04:00
|
|
|
shortPollEmailConfirmation();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::shortPollEmailConfirmation() {
|
|
|
|
if (_password.unconfirmedPattern.isEmpty()) {
|
|
|
|
_shortPollTimer.cancel();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_shortPollTimer.callOnce(kShortPollTimeout);
|
2018-03-31 05:45:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::showForm() {
|
|
|
|
if (!_bot) {
|
2018-04-15 19:06:53 +04:00
|
|
|
formFail(Lang::Hard::NoAuthorizationBot());
|
2018-03-31 05:45:40 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-08-04 00:48:00 +03:00
|
|
|
if (_password.unknownAlgo
|
|
|
|
|| !_password.newAlgo
|
|
|
|
|| !_password.newSecureAlgo) {
|
|
|
|
_view->showUpdateAppBox();
|
|
|
|
return;
|
2018-08-10 22:19:46 +03:00
|
|
|
} else if (_password.request) {
|
2018-07-10 19:41:11 +03:00
|
|
|
if (!_savedPasswordValue.isEmpty()) {
|
|
|
|
submitPassword(base::duplicate(_savedPasswordValue));
|
|
|
|
} else if (const auto saved = Auth().data().passportCredentials()) {
|
|
|
|
checkSavedPasswordSettings(*saved);
|
|
|
|
} else {
|
|
|
|
_view->showAskPassword();
|
|
|
|
}
|
2018-03-31 05:45:40 +04:00
|
|
|
} else {
|
|
|
|
_view->showNoPassword();
|
2018-03-19 20:22:27 +04:00
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-04-17 15:26:11 +04:00
|
|
|
bool FormController::applyPassword(const MTPDaccount_password &result) {
|
|
|
|
auto settings = PasswordSettings();
|
|
|
|
settings.hint = qs(result.vhint);
|
2018-04-20 20:42:51 +04:00
|
|
|
settings.hasRecovery = result.is_has_recovery();
|
|
|
|
settings.notEmptyPassport = result.is_has_secure_values();
|
2018-08-10 22:19:46 +03:00
|
|
|
settings.request = Core::ParseCloudPasswordCheckRequest(result);
|
2018-08-04 00:48:00 +03:00
|
|
|
settings.unknownAlgo = result.has_current_algo()
|
2018-08-10 22:19:46 +03:00
|
|
|
&& !settings.request;
|
2018-08-04 00:48:00 +03:00
|
|
|
settings.unconfirmedPattern = result.has_email_unconfirmed_pattern()
|
|
|
|
? qs(result.vemail_unconfirmed_pattern)
|
|
|
|
: QString();
|
|
|
|
settings.newAlgo = Core::ValidateNewCloudPasswordAlgo(
|
|
|
|
Core::ParseCloudPasswordAlgo(result.vnew_algo));
|
|
|
|
settings.newSecureAlgo = Core::ValidateNewSecureSecretAlgo(
|
|
|
|
Core::ParseSecureSecretAlgo(result.vnew_secure_algo));
|
2018-03-27 17:00:13 +04:00
|
|
|
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
|
2018-04-17 15:26:11 +04:00
|
|
|
return applyPassword(std::move(settings));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormController::applyPassword(PasswordSettings &&settings) {
|
|
|
|
if (_password != settings) {
|
|
|
|
_password = std::move(settings);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-03-31 05:45:40 +04:00
|
|
|
void FormController::cancel() {
|
2018-04-15 19:06:53 +04:00
|
|
|
if (!_submitSuccess && _serviceErrorText.isEmpty()) {
|
2018-04-13 22:14:14 +04:00
|
|
|
_view->show(Box<ConfirmBox>(
|
|
|
|
lang(lng_passport_stop_sure),
|
|
|
|
lang(lng_passport_stop),
|
2018-04-15 19:06:53 +04:00
|
|
|
[=] { cancelSure(); },
|
|
|
|
[=] { cancelAbort(); }));
|
2018-04-13 22:14:14 +04:00
|
|
|
} else {
|
|
|
|
cancelSure();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
void FormController::cancelAbort() {
|
|
|
|
if (_cancelled || _submitSuccess) {
|
|
|
|
return;
|
|
|
|
} else if (_suggestingRestart) {
|
|
|
|
suggestRestart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 22:14:14 +04:00
|
|
|
void FormController::cancelSure() {
|
2018-03-31 05:45:40 +04:00
|
|
|
if (!_cancelled) {
|
|
|
|
_cancelled = true;
|
2018-04-13 22:14:14 +04:00
|
|
|
|
2018-04-15 19:06:53 +04:00
|
|
|
if (!_request.callbackUrl.isEmpty()
|
|
|
|
&& (_serviceErrorText.isEmpty()
|
|
|
|
|| ForwardServiceErrorRequired(_serviceErrorText))) {
|
2018-05-18 20:03:31 +03:00
|
|
|
const auto url = qthelp::url_append_query_or_hash(
|
2018-04-15 19:06:53 +04:00
|
|
|
_request.callbackUrl,
|
|
|
|
(_submitSuccess
|
|
|
|
? "tg_passport=success"
|
|
|
|
: (_serviceErrorText.isEmpty()
|
|
|
|
? "tg_passport=cancel"
|
|
|
|
: "tg_passport=error&error=" + _serviceErrorText)));
|
2018-07-09 21:13:48 +03:00
|
|
|
UrlClickHandler::Open(url);
|
2018-04-15 19:06:53 +04:00
|
|
|
}
|
2018-04-13 22:14:14 +04:00
|
|
|
const auto timeout = _view->closeGetDuration();
|
|
|
|
App::CallDelayed(timeout, this, [=] {
|
2018-04-06 22:47:29 +04:00
|
|
|
_controller->clearPassportForm();
|
2018-03-31 05:45:40 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::lifetime &FormController::lifetime() {
|
|
|
|
return _lifetime;
|
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
FormController::~FormController() = default;
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
} // namespace Passport
|