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-19 20:22:27 +04:00
|
|
|
#include "passport/passport_form_box.h"
|
2018-03-21 08:35:32 +04:00
|
|
|
#include "passport/passport_edit_identity_box.h"
|
|
|
|
#include "passport/passport_encryption.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
#include "boxes/confirm_box.h"
|
|
|
|
#include "lang/lang_keys.h"
|
2018-03-19 20:22:27 +04:00
|
|
|
#include "base/openssl_help.h"
|
2018-03-18 12:51:14 +04:00
|
|
|
#include "mainwindow.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 {
|
|
|
|
|
|
|
|
QImage ReadImage(bytes::const_span buffer) {
|
|
|
|
return App::readImage(QByteArray::fromRawData(
|
|
|
|
reinterpret_cast<const char*>(buffer.data()),
|
|
|
|
buffer.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
} // 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,
|
|
|
|
const QString &publicKey)
|
|
|
|
: botId(botId)
|
|
|
|
, scope(scope)
|
|
|
|
, callbackUrl(callbackUrl)
|
|
|
|
, publicKey(publicKey) {
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
FormController::UploadScanData::~UploadScanData() {
|
2018-03-25 15:37:57 +04:00
|
|
|
if (fullId) {
|
|
|
|
Auth().uploader().cancel(fullId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FormController::EditFile::EditFile(
|
|
|
|
const File &fields,
|
2018-03-29 18:44:34 +04:00
|
|
|
std::unique_ptr<UploadScanData> &&uploadData)
|
2018-03-25 15:37:57 +04:00
|
|
|
: fields(std::move(fields))
|
2018-03-29 18:44:34 +04:00
|
|
|
, uploadData(std::move(uploadData)) {
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
FormController::Value::Value(Type type) : type(type) {
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
FormController::FormController(
|
|
|
|
not_null<Window::Controller*> controller,
|
|
|
|
const FormRequest &request)
|
|
|
|
: _controller(controller)
|
|
|
|
, _request(request) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::show() {
|
|
|
|
requestForm();
|
|
|
|
requestPassword();
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
bytes::vector FormController::passwordHashForAuth(
|
|
|
|
bytes::const_span password) const {
|
|
|
|
return openssl::Sha256(bytes::concatenate(
|
|
|
|
_password.salt,
|
|
|
|
password,
|
|
|
|
_password.salt));
|
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
void FormController::submitPassword(const QString &password) {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(!_password.salt.empty());
|
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-03-19 20:22:27 +04:00
|
|
|
}
|
2018-03-21 08:35:32 +04:00
|
|
|
const auto passwordBytes = password.toUtf8();
|
2018-03-19 20:22:27 +04:00
|
|
|
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_bytes(passwordHashForAuth(bytes::make_span(passwordBytes)))
|
2018-03-19 20:22:27 +04:00
|
|
|
)).handleFloodErrors(
|
|
|
|
).done([=](const MTPaccount_PasswordSettings &result) {
|
|
|
|
Expects(result.type() == mtpc_account_passwordSettings);
|
|
|
|
|
|
|
|
_passwordCheckRequestId = 0;
|
|
|
|
const auto &data = result.c_account_passwordSettings();
|
2018-03-27 17:00:13 +04:00
|
|
|
_password.confirmedEmail = qs(data.vemail);
|
|
|
|
validateSecureSecret(
|
|
|
|
bytes::make_span(data.vsecure_salt.v),
|
2018-03-27 16:16:00 +04:00
|
|
|
bytes::make_span(data.vsecure_secret.v),
|
2018-03-27 17:00:13 +04:00
|
|
|
bytes::make_span(passwordBytes));
|
2018-03-19 20:22:27 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
|
|
|
_passwordCheckRequestId = 0;
|
|
|
|
if (MTP::isFloodError(error)) {
|
|
|
|
_passwordError.fire(lang(lng_flood_error));
|
|
|
|
} else if (error.type() == qstr("PASSWORD_HASH_INVALID")) {
|
|
|
|
_passwordError.fire(lang(lng_passport_password_wrong));
|
|
|
|
} else {
|
|
|
|
_passwordError.fire_copy(error.type());
|
|
|
|
}
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
void FormController::validateSecureSecret(
|
|
|
|
bytes::const_span salt,
|
|
|
|
bytes::const_span encryptedSecret,
|
|
|
|
bytes::const_span password) {
|
|
|
|
if (!salt.empty() && !encryptedSecret.empty()) {
|
|
|
|
_secret = DecryptSecureSecret(salt, encryptedSecret, password);
|
|
|
|
if (_secret.empty()) {
|
2018-03-29 18:44:34 +04:00
|
|
|
_secretId = 0;
|
2018-03-27 17:00:13 +04:00
|
|
|
LOG(("API Error: Failed to decrypt secure secret. "
|
|
|
|
"Forgetting all files and data :("));
|
2018-03-29 18:44:34 +04:00
|
|
|
for (auto &value : _form.rows) {
|
|
|
|
if (!value.data.original.isEmpty()) {
|
|
|
|
resetValue(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2018-03-29 18:44:34 +04:00
|
|
|
_secretId = CountSecureSecretHash(_secret);
|
|
|
|
decryptValues();
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_secret.empty()) {
|
|
|
|
generateSecret(password);
|
|
|
|
}
|
|
|
|
_secretReady.fire({});
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::decryptValues() {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(!_secret.empty());
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
for (auto &value : _form.rows) {
|
|
|
|
if (value.data.original.isEmpty()) {
|
2018-03-27 17:00:13 +04:00
|
|
|
continue;
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
decryptValue(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::decryptValue(Value &value) {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(!_secret.empty());
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(!value.data.original.isEmpty());
|
2018-03-27 17:00:13 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
if (!validateValueSecrets(value)) {
|
|
|
|
resetValue(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
return;
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
value.data.parsed = DeserializeData(DecryptData(
|
|
|
|
bytes::make_span(value.data.original),
|
|
|
|
value.data.hash,
|
|
|
|
value.data.secret));
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
bool FormController::validateValueSecrets(Value &value) {
|
|
|
|
value.data.secret = DecryptValueSecret(
|
|
|
|
value.data.encryptedSecret,
|
2018-03-27 17:00:13 +04:00
|
|
|
_secret,
|
2018-03-29 18:44:34 +04:00
|
|
|
value.data.hash);
|
|
|
|
if (value.data.secret.empty()) {
|
|
|
|
LOG(("API Error: Could not decrypt data secret. "
|
2018-03-27 17:00:13 +04:00
|
|
|
"Forgetting files and data :("));
|
|
|
|
return false;
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
for (auto &file : value.files) {
|
|
|
|
file.secret = DecryptValueSecret(
|
|
|
|
file.encryptedSecret,
|
|
|
|
_secret,
|
|
|
|
file.hash);
|
|
|
|
if (file.secret.empty()) {
|
|
|
|
LOG(("API Error: Could not decrypt file secret. "
|
|
|
|
"Forgetting files and data :("));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const auto fileHashesSecrets = ranges::view::all(
|
|
|
|
value.files
|
2018-03-27 17:00:13 +04:00
|
|
|
) | ranges::view::transform([](File &file) {
|
2018-03-29 18:44:34 +04:00
|
|
|
return bytes::concatenate(file.hash, file.encryptedSecret);
|
2018-03-27 17:00:13 +04:00
|
|
|
});
|
|
|
|
const auto countedHash = openssl::Sha256(bytes::concatenate(
|
2018-03-29 18:44:34 +04:00
|
|
|
value.data.hash,
|
|
|
|
value.data.encryptedSecret,
|
|
|
|
bytes::concatenate(fileHashesSecrets)));
|
|
|
|
if (value.consistencyHash != countedHash) {
|
|
|
|
LOG(("API Error: Wrong hash after decrypting value secrets. "
|
2018-03-27 17:00:13 +04:00
|
|
|
"Forgetting files and data :("));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::resetValue(Value &value) {
|
|
|
|
value = 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString FormController::passwordHint() const {
|
|
|
|
return _password.hint;
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::uploadScan(int valueIndex, QByteArray &&content) {
|
2018-03-25 15:37:57 +04:00
|
|
|
Expects(_editBox != nullptr);
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto &value = _form.rows[valueIndex];
|
|
|
|
auto fileIndex = int(value.filesInEdit.size());
|
|
|
|
value.filesInEdit.emplace_back(
|
2018-03-29 00:40:42 +04:00
|
|
|
File(),
|
|
|
|
nullptr);
|
|
|
|
const auto fileId = rand_value<uint64>();
|
2018-03-29 18:44:34 +04:00
|
|
|
auto &file = value.filesInEdit.back();
|
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;
|
|
|
|
|
|
|
|
_scanUpdated.fire(collectScanInfo(file));
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
encryptScan(valueIndex, fileIndex, std::move(content));
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::encryptScan(
|
2018-03-29 18:44:34 +04:00
|
|
|
int valueIndex,
|
2018-03-29 00:40:42 +04:00
|
|
|
int fileIndex,
|
|
|
|
QByteArray &&content) {
|
|
|
|
Expects(_editBox != nullptr);
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
Expects(fileIndex >= 0
|
2018-03-29 18:44:34 +04:00
|
|
|
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
const auto &value = _form.rows[valueIndex];
|
|
|
|
const auto &file = value.filesInEdit[fileIndex].fields;
|
2018-03-25 15:37:57 +04:00
|
|
|
const auto weak = _editBox;
|
|
|
|
crl::async([
|
|
|
|
=,
|
2018-03-29 18:44:34 +04:00
|
|
|
fileId = file.id,
|
2018-03-25 15:37:57 +04:00
|
|
|
bytes = std::move(content),
|
2018-03-29 18:44:34 +04:00
|
|
|
fileSecret = file.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 {
|
|
|
|
if (weak) {
|
2018-03-29 00:40:42 +04:00
|
|
|
uploadEncryptedScan(
|
2018-03-29 18:44:34 +04:00
|
|
|
valueIndex,
|
2018-03-29 00:40:42 +04:00
|
|
|
fileIndex,
|
|
|
|
std::move(encrypted));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::deleteScan(
|
2018-03-29 18:44:34 +04:00
|
|
|
int valueIndex,
|
2018-03-29 00:40:42 +04:00
|
|
|
int fileIndex) {
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
Expects(fileIndex >= 0
|
2018-03-29 18:44:34 +04:00
|
|
|
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
|
2018-03-29 00:40:42 +04:00
|
|
|
file.deleted = !file.deleted;
|
|
|
|
_scanUpdated.fire(collectScanInfo(file));
|
|
|
|
}
|
|
|
|
|
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-03-29 00:40:42 +04:00
|
|
|
void FormController::uploadEncryptedScan(
|
2018-03-29 18:44:34 +04:00
|
|
|
int valueIndex,
|
2018-03-29 00:40:42 +04:00
|
|
|
int fileIndex,
|
2018-03-29 18:44:34 +04:00
|
|
|
UploadScanData &&data) {
|
2018-03-25 15:37:57 +04:00
|
|
|
Expects(_editBox != nullptr);
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
Expects(fileIndex >= 0
|
2018-03-29 18:44:34 +04:00
|
|
|
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
|
2018-03-25 15:37:57 +04:00
|
|
|
|
|
|
|
subscribeToUploader();
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
|
|
|
|
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
|
|
|
|
|
|
|
_scanUpdated.fire(collectScanInfo(*file));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
_scanUpdated.fire(collectScanInfo(*file));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
_scanUpdated.fire(collectScanInfo(*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-03-25 15:37:57 +04:00
|
|
|
rpl::producer<ScanInfo> FormController::scanUpdated() const {
|
|
|
|
return _scanUpdated.events();
|
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
void FormController::fillRows(
|
|
|
|
base::lambda<void(
|
|
|
|
QString title,
|
|
|
|
QString description,
|
|
|
|
bool ready)> callback) {
|
2018-03-29 18:44:34 +04:00
|
|
|
for (const auto &value : _form.rows) {
|
|
|
|
switch (value.type) {
|
|
|
|
case Value::Type::Identity:
|
2018-03-19 20:22:27 +04:00
|
|
|
callback(
|
|
|
|
lang(lng_passport_identity_title),
|
|
|
|
lang(lng_passport_identity_description),
|
|
|
|
false);
|
|
|
|
break;
|
2018-03-29 18:44:34 +04:00
|
|
|
case Value::Type::Address:
|
2018-03-19 20:22:27 +04:00
|
|
|
callback(
|
|
|
|
lang(lng_passport_address_title),
|
|
|
|
lang(lng_passport_address_description),
|
|
|
|
false);
|
|
|
|
break;
|
2018-03-29 18:44:34 +04:00
|
|
|
case Value::Type::Phone:
|
2018-03-19 20:22:27 +04:00
|
|
|
callback(
|
|
|
|
lang(lng_passport_phone_title),
|
|
|
|
App::self()->phone(),
|
|
|
|
true);
|
|
|
|
break;
|
2018-03-29 18:44:34 +04:00
|
|
|
case Value::Type::Email:
|
2018-03-19 20:22:27 +04:00
|
|
|
callback(
|
|
|
|
lang(lng_passport_email_title),
|
|
|
|
lang(lng_passport_email_description),
|
|
|
|
false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::editValue(int index) {
|
|
|
|
Expects(index >= 0 && index < _form.rows.size());
|
|
|
|
|
|
|
|
auto &value = _form.rows[index];
|
|
|
|
loadFiles(value.files);
|
|
|
|
value.filesInEdit = ranges::view::all(
|
|
|
|
value.files
|
|
|
|
) | ranges::view::transform([](const File &file) {
|
|
|
|
return EditFile(file, nullptr);
|
|
|
|
}) | ranges::to_vector;
|
2018-03-21 08:35:32 +04:00
|
|
|
|
|
|
|
auto box = [&]() -> object_ptr<BoxContent> {
|
2018-03-29 18:44:34 +04:00
|
|
|
switch (value.type) {
|
|
|
|
case Value::Type::Identity:
|
2018-03-25 15:37:57 +04:00
|
|
|
return Box<IdentityBox>(
|
|
|
|
this,
|
|
|
|
index,
|
2018-03-29 18:44:34 +04:00
|
|
|
valueDataIdentity(value),
|
|
|
|
valueFilesIdentity(value));
|
2018-03-21 08:35:32 +04:00
|
|
|
}
|
|
|
|
return { nullptr };
|
|
|
|
}();
|
|
|
|
if (box) {
|
|
|
|
_editBox = Ui::show(std::move(box), LayerOption::KeepOther);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-29 00:40:42 +04:00
|
|
|
void FormController::loadFiles(std::vector<File> &files) {
|
|
|
|
for (auto &file : files) {
|
|
|
|
if (!file.image.isNull()) {
|
|
|
|
file.downloadOffset = file.size;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
const auto key = FileKey{ file.id, file.dcId };
|
|
|
|
const auto i = _fileLoaders.find(key);
|
|
|
|
if (i == _fileLoaders.end()) {
|
2018-03-29 00:40:42 +04:00
|
|
|
file.downloadOffset = 0;
|
2018-03-25 15:37:57 +04:00
|
|
|
const auto [i, 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 = i->second.get();
|
|
|
|
loader->connect(loader, &mtpFileLoader::progress, [=] {
|
|
|
|
if (loader->finished()) {
|
2018-03-29 00:40:42 +04:00
|
|
|
fileLoadDone(key, loader->bytes());
|
|
|
|
} else {
|
|
|
|
fileLoadProgress(key, loader->currentOffset());
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
loader->connect(loader, &mtpFileLoader::failed, [=] {
|
2018-03-29 00:40:42 +04:00
|
|
|
fileLoadFail(key);
|
2018-03-25 15:37:57 +04:00
|
|
|
});
|
|
|
|
loader->start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
_scanUpdated.fire(collectScanInfo(*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;
|
|
|
|
_scanUpdated.fire(collectScanInfo(*fileInEdit));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
_scanUpdated.fire(collectScanInfo(*fileInEdit));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ScanInfo FormController::collectScanInfo(const EditFile &file) const {
|
|
|
|
const auto status = [&] {
|
|
|
|
if (file.deleted) {
|
|
|
|
return QString("deleted");
|
|
|
|
} else if (file.fields.accessHash) {
|
|
|
|
if (file.fields.downloadOffset < 0) {
|
|
|
|
return QString("download failed");
|
|
|
|
} else if (file.fields.downloadOffset < file.fields.size) {
|
|
|
|
return QString("downloading %1 / %2"
|
|
|
|
).arg(file.fields.downloadOffset
|
|
|
|
).arg(file.fields.size);
|
|
|
|
} else {
|
2018-03-29 18:44:34 +04:00
|
|
|
return QString("uploaded ")
|
|
|
|
+ langDateTimeFull(ParseDateTime(file.fields.date));
|
2018-03-29 00:40:42 +04:00
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
} else if (file.uploadData) {
|
|
|
|
if (file.uploadData->offset < 0) {
|
2018-03-29 00:40:42 +04:00
|
|
|
return QString("upload failed");
|
2018-03-29 18:44:34 +04:00
|
|
|
} else if (file.uploadData->fullId) {
|
2018-03-29 00:40:42 +04:00
|
|
|
return QString("uploading %1 / %2"
|
2018-03-29 18:44:34 +04:00
|
|
|
).arg(file.uploadData->offset
|
|
|
|
).arg(file.uploadData->bytes.size());
|
2018-03-29 00:40:42 +04:00
|
|
|
} else {
|
|
|
|
return QString("upload ready");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return QString("preparing");
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
return {
|
|
|
|
FileKey{ file.fields.id, file.fields.dcId },
|
|
|
|
status,
|
|
|
|
file.fields.image };
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
IdentityData FormController::valueDataIdentity(const Value &value) const {
|
|
|
|
const auto &map = value.data.parsed;
|
2018-03-21 08:35:32 +04:00
|
|
|
auto result = IdentityData();
|
|
|
|
if (const auto i = map.find(qsl("first_name")); i != map.cend()) {
|
|
|
|
result.name = i->second;
|
|
|
|
}
|
|
|
|
if (const auto i = map.find(qsl("last_name")); i != map.cend()) {
|
|
|
|
result.surname = i->second;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
std::vector<ScanInfo> FormController::valueFilesIdentity(
|
|
|
|
const Value &value) const {
|
2018-03-25 15:37:57 +04:00
|
|
|
auto result = std::vector<ScanInfo>();
|
2018-03-29 18:44:34 +04:00
|
|
|
for (const auto &file : value.filesInEdit) {
|
2018-03-29 00:40:42 +04:00
|
|
|
result.push_back(collectScanInfo(file));
|
2018-03-25 15:37:57 +04:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
void FormController::saveValueIdentity(
|
2018-03-21 08:35:32 +04:00
|
|
|
int index,
|
|
|
|
const IdentityData &data) {
|
|
|
|
Expects(_editBox != nullptr);
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(index >= 0 && index < _form.rows.size());
|
|
|
|
Expects(_form.rows[index].type == Value::Type::Identity);
|
2018-03-21 08:35:32 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
_form.rows[index].data.parsed[qsl("first_name")] = data.name;
|
|
|
|
_form.rows[index].data.parsed[qsl("last_name")] = data.surname;
|
2018-03-21 08:35:32 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
saveIdentity(index);
|
2018-03-21 08:35:32 +04:00
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
void FormController::saveIdentity(int index) {
|
2018-03-29 18:44:34 +04:00
|
|
|
Expects(index >= 0 && index < _form.rows.size());
|
|
|
|
Expects(_form.rows[index].type == Value::Type::Identity);
|
2018-03-21 08:35:32 +04:00
|
|
|
|
|
|
|
if (_secret.empty()) {
|
2018-03-27 17:00:13 +04:00
|
|
|
_secretCallbacks.push_back([=] {
|
|
|
|
saveIdentity(index);
|
2018-03-21 08:35:32 +04:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
_editBox->closeBox();
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto &value = _form.rows[index];
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
auto inputFiles = QVector<MTPInputSecureFile>();
|
2018-03-29 18:44:34 +04:00
|
|
|
inputFiles.reserve(value.filesInEdit.size());
|
|
|
|
for (const auto &file : value.filesInEdit) {
|
2018-03-29 00:40:42 +04:00
|
|
|
if (file.deleted) {
|
|
|
|
continue;
|
2018-03-29 18:44:34 +04:00
|
|
|
} else if (const auto uploadData = file.uploadData.get()) {
|
2018-03-27 17:00:13 +04:00
|
|
|
inputFiles.push_back(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),
|
|
|
|
MTP_bytes(file.fields.encryptedSecret)));
|
2018-03-25 15:37:57 +04:00
|
|
|
} else {
|
2018-03-27 17:00:13 +04:00
|
|
|
inputFiles.push_back(MTP_inputSecureFile(
|
2018-03-25 15:37:57 +04:00
|
|
|
MTP_long(file.fields.id),
|
|
|
|
MTP_long(file.fields.accessHash)));
|
|
|
|
}
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
|
|
|
|
if (value.data.secret.empty()) {
|
|
|
|
value.data.secret = GenerateSecretBytes();
|
2018-03-27 17:00:13 +04:00
|
|
|
}
|
|
|
|
const auto encryptedData = EncryptData(
|
2018-03-29 18:44:34 +04:00
|
|
|
SerializeData(value.data.parsed),
|
|
|
|
value.data.secret);
|
|
|
|
value.data.hash = encryptedData.hash;
|
|
|
|
value.data.encryptedSecret = EncryptValueSecret(
|
|
|
|
value.data.secret,
|
|
|
|
_secret,
|
|
|
|
value.data.hash);
|
|
|
|
|
|
|
|
const auto fileHashesSecrets = ranges::view::all(
|
|
|
|
value.filesInEdit
|
2018-03-29 00:40:42 +04:00
|
|
|
) | ranges::view::filter([](const EditFile &file) {
|
|
|
|
return !file.deleted;
|
|
|
|
}) | ranges::view::transform([](const EditFile &file) {
|
2018-03-29 18:44:34 +04:00
|
|
|
return bytes::concatenate(
|
|
|
|
file.fields.hash,
|
|
|
|
file.fields.encryptedSecret);
|
2018-03-27 17:00:13 +04:00
|
|
|
});
|
2018-03-29 18:44:34 +04:00
|
|
|
value.consistencyHash = openssl::Sha256(bytes::concatenate(
|
|
|
|
value.data.hash,
|
|
|
|
value.data.encryptedSecret,
|
|
|
|
bytes::concatenate(fileHashesSecrets)));
|
2018-03-25 15:37:57 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
request(MTPaccount_SaveSecureValue(
|
|
|
|
MTP_inputSecureValueIdentity(
|
|
|
|
MTP_secureData(
|
|
|
|
MTP_bytes(encryptedData.bytes),
|
2018-03-29 18:44:34 +04:00
|
|
|
MTP_bytes(value.data.hash),
|
|
|
|
MTP_bytes(value.data.encryptedSecret)),
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_vector<MTPInputSecureFile>(inputFiles),
|
2018-03-29 18:44:34 +04:00
|
|
|
MTP_bytes(value.consistencyHash)),
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_long(CountSecureSecretHash(_secret))
|
|
|
|
)).done([=](const MTPSecureValueSaved &result) {
|
2018-03-29 00:40:42 +04:00
|
|
|
Expects(result.type() == mtpc_secureValueSaved);
|
|
|
|
|
|
|
|
const auto &data = result.c_secureValueSaved();
|
2018-03-29 18:44:34 +04:00
|
|
|
_form.rows[index].files = parseFiles(
|
2018-03-29 00:40:42 +04:00
|
|
|
data.vfiles.v,
|
2018-03-29 18:44:34 +04:00
|
|
|
base::take(_form.rows[index].filesInEdit));
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther);
|
2018-03-25 15:37:57 +04:00
|
|
|
}).fail([=](const RPCError &error) {
|
2018-03-27 17:00:13 +04:00
|
|
|
Ui::show(Box<InformBox>("Error saving value."));
|
2018-03-21 08:35:32 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
void FormController::generateSecret(bytes::const_span password) {
|
2018-03-21 08:35:32 +04:00
|
|
|
if (_saveSecretRequestId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto secret = GenerateSecretBytes();
|
2018-03-27 17:00:13 +04:00
|
|
|
|
|
|
|
auto randomSaltPart = bytes::vector(8);
|
|
|
|
bytes::set_random(randomSaltPart);
|
|
|
|
auto newSecureSaltFull = bytes::concatenate(
|
|
|
|
_password.newSecureSalt,
|
|
|
|
randomSaltPart);
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto secureSecretId = CountSecureSecretHash(secret);
|
|
|
|
auto encryptedSecret = EncryptSecureSecret(
|
2018-03-27 17:00:13 +04:00
|
|
|
newSecureSaltFull,
|
2018-03-29 18:44:34 +04:00
|
|
|
secret,
|
|
|
|
password);
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
const auto hashForAuth = openssl::Sha256(bytes::concatenate(
|
|
|
|
_password.salt,
|
|
|
|
password,
|
|
|
|
_password.salt));
|
|
|
|
|
2018-03-21 08:35:32 +04:00
|
|
|
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
|
|
|
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_bytes(hashForAuth),
|
2018-03-21 08:35:32 +04:00
|
|
|
MTP_account_passwordInputSettings(
|
|
|
|
MTP_flags(Flag::f_new_secure_secret),
|
|
|
|
MTPbytes(), // new_salt
|
|
|
|
MTPbytes(), // new_password_hash
|
|
|
|
MTPstring(), // hint
|
|
|
|
MTPstring(), // email
|
2018-03-27 17:00:13 +04:00
|
|
|
MTP_bytes(newSecureSaltFull),
|
|
|
|
MTP_bytes(encryptedSecret),
|
2018-03-29 18:44:34 +04:00
|
|
|
MTP_long(secureSecretId))
|
2018-03-21 08:35:32 +04:00
|
|
|
)).done([=](const MTPBool &result) {
|
|
|
|
_saveSecretRequestId = 0;
|
|
|
|
_secret = secret;
|
2018-03-29 18:44:34 +04:00
|
|
|
_secretId = secureSecretId;
|
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) {
|
|
|
|
// #TODO wrong password hash error?
|
|
|
|
Ui::show(Box<InformBox>("Saving encrypted value failed."));
|
|
|
|
_saveSecretRequestId = 0;
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
void FormController::requestForm() {
|
|
|
|
auto normalizedKey = _request.publicKey;
|
|
|
|
normalizedKey.replace("\r\n", "\n");
|
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),
|
|
|
|
MTP_bytes(normalizedKey.toUtf8())
|
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) {
|
|
|
|
formFail(error);
|
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
template <typename DataType>
|
2018-03-29 18:44:34 +04:00
|
|
|
auto FormController::parseEncryptedValue(
|
|
|
|
Value::Type type,
|
|
|
|
const DataType &data) const -> Value {
|
2018-03-27 17:00:13 +04:00
|
|
|
Expects(data.vdata.type() == mtpc_secureData);
|
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto result = Value(type);
|
2018-03-27 17:00:13 +04:00
|
|
|
if (data.has_verified()) {
|
|
|
|
result.verification = parseVerified(data.vverified);
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
result.consistencyHash = bytes::make_vector(data.vhash.v);
|
2018-03-27 17:00:13 +04:00
|
|
|
const auto &fields = data.vdata.c_secureData();
|
2018-03-29 18:44:34 +04:00
|
|
|
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);
|
2018-03-29 00:40:42 +04:00
|
|
|
result.files = parseFiles(data.vfiles.v);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
auto index = 0;
|
|
|
|
for (const auto &file : data) {
|
2018-03-27 17:00:13 +04:00
|
|
|
switch (file.type()) {
|
|
|
|
case mtpc_secureFileEmpty: {
|
2018-03-29 00:40:42 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
} break;
|
|
|
|
case mtpc_secureFile: {
|
|
|
|
const auto &fields = file.c_secureFile();
|
|
|
|
auto normal = File();
|
|
|
|
normal.id = fields.vid.v;
|
|
|
|
normal.accessHash = fields.vaccess_hash.v;
|
|
|
|
normal.size = fields.vsize.v;
|
2018-03-29 18:44:34 +04:00
|
|
|
normal.date = fields.vdate.v;
|
2018-03-27 17:00:13 +04:00
|
|
|
normal.dcId = fields.vdc_id.v;
|
2018-03-29 18:44:34 +04:00
|
|
|
normal.hash = bytes::make_vector(fields.vfile_hash.v);
|
|
|
|
normal.encryptedSecret = bytes::make_vector(fields.vsecret.v);
|
|
|
|
fillDownloadedFile(normal, editData);
|
2018-03-29 00:40:42 +04:00
|
|
|
result.push_back(std::move(normal));
|
2018-03-27 17:00:13 +04:00
|
|
|
} break;
|
|
|
|
}
|
2018-03-29 00:40:42 +04:00
|
|
|
++index;
|
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-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-03-27 17:00:13 +04:00
|
|
|
template <typename DataType>
|
2018-03-29 18:44:34 +04:00
|
|
|
auto FormController::parsePlainTextValue(
|
|
|
|
Value::Type type,
|
2018-03-27 17:00:13 +04:00
|
|
|
const QByteArray &value,
|
2018-03-29 18:44:34 +04:00
|
|
|
const DataType &data) const -> Value {
|
|
|
|
auto result = Value(type);
|
2018-03-27 17:00:13 +04:00
|
|
|
const auto check = bytes::compare(
|
|
|
|
bytes::make_span(data.vhash.v),
|
|
|
|
openssl::Sha256(bytes::make_span(value)));
|
|
|
|
if (check != 0) {
|
|
|
|
LOG(("API Error: Bad hash for plain text value. "
|
|
|
|
"Value '%1', hash '%2'"
|
|
|
|
).arg(QString::fromUtf8(value)
|
|
|
|
).arg(Logs::mb(data.vhash.v.data(), data.vhash.v.size()).str()
|
|
|
|
));
|
|
|
|
return result;
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
result.data.parsed[QString("value")] = QString::fromUtf8(value);
|
2018-03-27 17:00:13 +04:00
|
|
|
if (data.has_verified()) {
|
|
|
|
result.verification = parseVerified(data.vverified);
|
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
result.consistencyHash = bytes::make_vector(data.vhash.v);
|
2018-03-27 17:00:13 +04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto FormController::parseValue(
|
2018-03-29 18:44:34 +04:00
|
|
|
const MTPSecureValue &value) const -> Value {
|
2018-03-18 12:51:14 +04:00
|
|
|
switch (value.type()) {
|
2018-03-27 17:00:13 +04:00
|
|
|
case mtpc_secureValueIdentity: {
|
2018-03-29 18:44:34 +04:00
|
|
|
return parseEncryptedValue(
|
|
|
|
Value::Type::Identity,
|
2018-03-27 17:00:13 +04:00
|
|
|
value.c_secureValueIdentity());
|
2018-03-18 12:51:14 +04:00
|
|
|
} break;
|
2018-03-27 17:00:13 +04:00
|
|
|
case mtpc_secureValueAddress: {
|
2018-03-29 18:44:34 +04:00
|
|
|
return parseEncryptedValue(
|
|
|
|
Value::Type::Address,
|
2018-03-27 17:00:13 +04:00
|
|
|
value.c_secureValueAddress());
|
2018-03-18 12:51:14 +04:00
|
|
|
} break;
|
2018-03-27 17:00:13 +04:00
|
|
|
case mtpc_secureValuePhone: {
|
|
|
|
const auto &data = value.c_secureValuePhone();
|
2018-03-29 18:44:34 +04:00
|
|
|
return parsePlainTextValue(
|
|
|
|
Value::Type::Phone,
|
2018-03-27 17:00:13 +04:00
|
|
|
data.vphone.v,
|
|
|
|
data);
|
2018-03-18 12:51:14 +04:00
|
|
|
} break;
|
2018-03-27 17:00:13 +04:00
|
|
|
case mtpc_secureValueEmail: {
|
|
|
|
const auto &data = value.c_secureValueEmail();
|
2018-03-29 18:44:34 +04:00
|
|
|
return parsePlainTextValue(
|
|
|
|
Value::Type::Phone,
|
2018-03-27 17:00:13 +04:00
|
|
|
data.vemail.v,
|
|
|
|
data);
|
2018-03-18 12:51:14 +04:00
|
|
|
} break;
|
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
Unexpected("secureValue type.");
|
|
|
|
}
|
|
|
|
|
|
|
|
auto FormController::parseVerified(const MTPSecureValueVerified &data) const
|
|
|
|
-> Verification {
|
|
|
|
Expects(data.type() == mtpc_secureValueVerified);
|
|
|
|
|
|
|
|
const auto &fields = data.c_secureValueVerified();
|
2018-03-29 18:44:34 +04:00
|
|
|
return Verification{ fields.vdate.v };
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
|
2018-03-29 18:44:34 +04:00
|
|
|
for (auto &value : _form.rows) {
|
|
|
|
for (auto &file : value.filesInEdit) {
|
|
|
|
if (file.uploadData && file.uploadData->fullId == fullId) {
|
2018-03-27 17:00:13 +04:00
|
|
|
return &file;
|
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-03-29 18:44:34 +04:00
|
|
|
for (auto &value : _form.rows) {
|
|
|
|
for (auto &file : value.filesInEdit) {
|
2018-03-29 00:40:42 +04:00
|
|
|
if (file.fields.dcId == key.dcId && file.fields.id == key.id) {
|
|
|
|
return &file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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*> {
|
|
|
|
for (auto &value : _form.rows) {
|
|
|
|
for (auto &file : value.files) {
|
2018-03-27 17:00:13 +04:00
|
|
|
if (file.dcId == key.dcId && file.id == key.id) {
|
2018-03-29 18:44:34 +04:00
|
|
|
return { &value, &file };
|
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) {
|
|
|
|
parseForm(result);
|
2018-03-19 20:22:27 +04:00
|
|
|
if (!_passwordRequestId) {
|
|
|
|
showForm();
|
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
|
|
|
|
Expects(result.type() == mtpc_account_authorizationForm);
|
|
|
|
|
|
|
|
const auto &data = result.c_account_authorizationForm();
|
2018-03-27 17:00:13 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
auto values = std::vector<Value>();
|
2018-03-27 17:00:13 +04:00
|
|
|
for (const auto &value : data.vvalues.v) {
|
|
|
|
values.push_back(parseValue(value));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
2018-03-29 18:44:34 +04:00
|
|
|
const auto findValue = [&](Value::Type type) -> Value* {
|
2018-03-27 17:00:13 +04:00
|
|
|
for (auto &value : values) {
|
|
|
|
if (value.type == type) {
|
|
|
|
return &value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
};
|
2018-03-18 12:51:14 +04:00
|
|
|
|
2018-03-27 17:00:13 +04:00
|
|
|
App::feedUsers(data.vusers);
|
|
|
|
for (const auto &required : data.vrequired_types.v) {
|
2018-03-29 18:44:34 +04:00
|
|
|
using Type = Value::Type;
|
2018-03-18 12:51:14 +04:00
|
|
|
|
|
|
|
const auto type = [&] {
|
2018-03-27 17:00:13 +04:00
|
|
|
switch (required.type()) {
|
|
|
|
case mtpc_secureValueTypeIdentity: return Type::Identity;
|
|
|
|
case mtpc_secureValueTypeAddress: return Type::Address;
|
|
|
|
case mtpc_secureValueTypeEmail: return Type::Email;
|
|
|
|
case mtpc_secureValueTypePhone: return Type::Phone;
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
2018-03-27 17:00:13 +04:00
|
|
|
Unexpected("Type in secureValueType type.");
|
2018-03-18 12:51:14 +04:00
|
|
|
}();
|
2018-03-27 17:00:13 +04:00
|
|
|
|
2018-03-29 18:44:34 +04:00
|
|
|
if (auto value = findValue(type)) {
|
|
|
|
_form.rows.push_back(std::move(*value));
|
2018-03-27 17:00:13 +04:00
|
|
|
} else {
|
2018-03-29 18:44:34 +04:00
|
|
|
_form.rows.push_back(Value(type));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_bot = App::userLoaded(_request.botId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::showForm() {
|
|
|
|
if (!_bot) {
|
|
|
|
Ui::show(Box<InformBox>("Could not get authorization bot."));
|
|
|
|
return;
|
|
|
|
}
|
2018-03-19 20:22:27 +04:00
|
|
|
Ui::show(Box<FormBox>(this));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::formFail(const RPCError &error) {
|
2018-03-19 20:22:27 +04:00
|
|
|
Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::requestPassword() {
|
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) {
|
|
|
|
formFail(error);
|
2018-03-18 12:51:14 +04:00
|
|
|
}).send();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::passwordDone(const MTPaccount_Password &result) {
|
|
|
|
switch (result.type()) {
|
|
|
|
case mtpc_account_noPassword:
|
2018-03-19 20:22:27 +04:00
|
|
|
parsePassword(result.c_account_noPassword());
|
2018-03-18 12:51:14 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case mtpc_account_password:
|
2018-03-19 20:22:27 +04:00
|
|
|
parsePassword(result.c_account_password());
|
2018-03-18 12:51:14 +04:00
|
|
|
break;
|
|
|
|
}
|
2018-03-19 20:22:27 +04:00
|
|
|
if (!_formRequestId) {
|
|
|
|
showForm();
|
|
|
|
}
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void FormController::passwordFail(const RPCError &error) {
|
|
|
|
Ui::show(Box<InformBox>("Could not get authorization form."));
|
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
void FormController::parsePassword(const MTPDaccount_noPassword &result) {
|
|
|
|
_password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
|
2018-03-27 17:00:13 +04:00
|
|
|
_password.newSalt = bytes::make_vector(result.vnew_salt.v);
|
|
|
|
_password.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
|
|
|
|
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-03-19 20:22:27 +04:00
|
|
|
void FormController::parsePassword(const MTPDaccount_password &result) {
|
|
|
|
_password.hint = qs(result.vhint);
|
|
|
|
_password.hasRecovery = mtpIsTrue(result.vhas_recovery);
|
2018-03-27 17:00:13 +04:00
|
|
|
_password.salt = bytes::make_vector(result.vcurrent_salt.v);
|
2018-03-19 20:22:27 +04:00
|
|
|
_password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
|
2018-03-27 17:00:13 +04:00
|
|
|
_password.newSalt = bytes::make_vector(result.vnew_salt.v);
|
|
|
|
_password.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
|
|
|
|
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
|
2018-03-18 12:51:14 +04:00
|
|
|
}
|
|
|
|
|
2018-03-25 15:37:57 +04:00
|
|
|
FormController::~FormController() = default;
|
|
|
|
|
2018-03-18 12:51:14 +04:00
|
|
|
} // namespace Passport
|