Submit passport results.

This commit is contained in:
John Preston 2018-04-12 19:45:04 +04:00
parent c20cf243db
commit ead31757d7
18 changed files with 664 additions and 403 deletions

View file

@ -44,4 +44,16 @@ bool is_ipv6(const QString &ip) {
return ip.indexOf('.') < 0 && ip.indexOf(':') >= 0;
}
QString url_append_query(const QString &url, const QString &add) {
const auto query = ranges::find(url, '?');
const auto hash = ranges::find(url, '#');
const auto base = url.mid(0, hash - url.begin());
const auto added = base
+ (hash <= query ? '?' : '&')
+ add;
const auto result = added
+ (hash < url.end() ? url.mid(hash - url.begin()) : QString());
return result;
}
} // namespace qthelp

View file

@ -22,7 +22,11 @@ enum class UrlParamNameTransform {
ToLower,
};
// Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map.
QMap<QString, QString> url_parse_params(const QString &params, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform);
QMap<QString, QString> url_parse_params(
const QString &params,
UrlParamNameTransform transform = UrlParamNameTransform::NoTransform);
QString url_append_query(const QString &url, const QString &add);
bool is_ipv6(const QString &ip);

View file

@ -841,13 +841,15 @@ bool Messenger::openLocalUrl(const QString &url) {
const auto scope = params.value("scope", QString());
const auto callback = params.value("callback_url", QString());
const auto publicKey = params.value("public_key", QString());
const auto payload = params.value("payload", QString());
if (const auto window = App::wnd()) {
if (const auto controller = window->controller()) {
controller->showPassportForm(Passport::FormRequest(
botId,
scope,
callback,
publicKey));
publicKey,
payload));
return true;
}
}

View file

@ -164,6 +164,29 @@ public:
}
return result;
}
bytes::vector encryptOAEPpadding(bytes::const_span data) const {
Expects(isValid());
const auto resultSize = RSA_size(_rsa);
auto result = bytes::vector(resultSize, gsl::byte{});
const auto encryptedSize = RSA_public_encrypt(
data.size(),
reinterpret_cast<const unsigned char*>(data.data()),
reinterpret_cast<unsigned char*>(result.data()),
_rsa,
RSA_PKCS1_OAEP_PADDING);
if (encryptedSize != resultSize) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, "
"key fp: %1, result: %2, error: %3"
).arg(getFingerPrint()
).arg(encryptedSize
).arg(ERR_error_string(ERR_get_error(), 0)
));
return {};
}
return result;
}
~Private() {
RSA_free(_rsa);
}
@ -236,5 +259,10 @@ bytes::vector RSAPublicKey::decrypt(bytes::const_span data) const {
return _private->decrypt(data);
}
bytes::vector RSAPublicKey::encryptOAEPpadding(
bytes::const_span data) const {
return _private->encryptOAEPpadding(data);
}
} // namespace internal
} // namespace MTP

View file

@ -37,6 +37,9 @@ public:
// data has exactly 256 chars to be decrypted
bytes::vector decrypt(bytes::const_span data) const;
// data has lequal than 215 chars to be decrypted
bytes::vector encryptOAEPpadding(bytes::const_span data) const;
private:
class Private;
std::shared_ptr<Private> _private;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_encryption.h"
#include "base/openssl_help.h"
#include "mtproto/rsa_public_key.h"
namespace Passport {
namespace {
@ -314,14 +315,6 @@ bytes::vector PrepareValueHash(
return openssl::Sha256(bytes::concatenate(dataHash, valueSecret));
}
bytes::vector PrepareFilesHash(
gsl::span<bytes::const_span> fileHashes,
bytes::const_span valueSecret) {
return openssl::Sha256(bytes::concatenate(
bytes::concatenate(fileHashes),
valueSecret));
}
bytes::vector EncryptValueSecret(
bytes::const_span valueSecret,
bytes::const_span secret,
@ -350,4 +343,11 @@ uint64 CountSecureSecretHash(bytes::const_span secret) {
return *reinterpret_cast<const uint64*>(part.data());
}
bytes::vector EncryptCredentialsSecret(
bytes::const_span secret,
bytes::const_span publicKey) {
const auto key = MTP::internal::RSAPublicKey(publicKey);
return key.encryptOAEPpadding(secret);
}
} // namespace Passport

View file

@ -54,10 +54,10 @@ bytes::vector DecryptValueSecret(
bytes::const_span secret,
bytes::const_span valueHash);
bytes::vector PrepareFilesHash(
gsl::span<bytes::const_span> fileHashes,
bytes::const_span valueSecret);
uint64 CountSecureSecretHash(bytes::const_span secret);
bytes::vector EncryptCredentialsSecret(
bytes::const_span secret,
bytes::const_span publicKey);
} // namespace Passport

View file

@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "base/openssl_help.h"
#include "base/qthelp_url.h"
#include "mainwindow.h"
#include "window/window_controller.h"
#include "core/click_handler_types.h"
#include "auth_session.h"
#include "storage/localimageloader.h"
#include "storage/localstorage.h"
@ -46,17 +48,86 @@ Value::Type ConvertType(const MTPSecureValueType &type) {
Unexpected("Type in secureValueType type.");
};
MTPSecureValueType ConvertType(Value::Type type) {
switch (type) {
case Value::Type::PersonalDetails:
return MTP_secureValueTypePersonalDetails();
case Value::Type::Passport:
return MTP_secureValueTypePassport();
case Value::Type::DriverLicense:
return MTP_secureValueTypeDriverLicense();
case Value::Type::IdentityCard:
return MTP_secureValueTypeIdentityCard();
case Value::Type::Address:
return MTP_secureValueTypeAddress();
case Value::Type::UtilityBill:
return MTP_secureValueTypeUtilityBill();
case Value::Type::BankStatement:
return MTP_secureValueTypeBankStatement();
case Value::Type::RentalAgreement:
return MTP_secureValueTypeRentalAgreement();
case Value::Type::Phone:
return MTP_secureValueTypePhone();
case Value::Type::Email:
return MTP_secureValueTypeEmail();
}
Unexpected("Type in FormController::submit.");
};
QJsonObject GetJSONFromMap(
const std::map<QString, bytes::const_span> &map) {
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 }
});
}
FormRequest PreprocessRequest(const FormRequest &request) {
auto result = request;
result.publicKey.replace("\r\n", "\n");
return result;
}
QString ValueCredentialsKey(Value::Type type) {
switch (type) {
case Value::Type::PersonalDetails: return "personal_details";
case Value::Type::Passport: return "passport";
case Value::Type::DriverLicense: return "driver_license";
case Value::Type::IdentityCard: return "identity_card";
case Value::Type::Address: return "address";
case Value::Type::UtilityBill: return "utility_bill";
case Value::Type::BankStatement: return "bank_statement";
case Value::Type::RentalAgreement: return "rental_agreement";
case Value::Type::Phone:
case Value::Type::Email: return QString();
}
Unexpected("Type in ValueCredentialsKey.");
}
} // namespace
FormRequest::FormRequest(
UserId botId,
const QString &scope,
const QString &callbackUrl,
const QString &publicKey)
const QString &publicKey,
const QString &payload)
: botId(botId)
, scope(scope)
, callbackUrl(callbackUrl)
, publicKey(publicKey) {
, publicKey(publicKey)
, payload(payload) {
}
EditFile::EditFile(
@ -111,7 +182,7 @@ FormController::FormController(
not_null<Window::Controller*> controller,
const FormRequest &request)
: _controller(controller)
, _request(request)
, _request(PreprocessRequest(request))
, _view(std::make_unique<PanelController>(this)) {
}
@ -136,6 +207,99 @@ bytes::vector FormController::passwordHashForAuth(
_password.salt));
}
auto FormController::prepareFinalData() const -> FinalData {
auto hashes = QVector<MTPSecureValueHash>();
auto secureData = QJsonObject();
const auto addValueToJSON = [&](
const QString &key,
not_null<const Value*> value) {
auto object = QJsonObject();
if (!value->data.parsed.fields.empty()) {
object.insert("data", GetJSONFromMap({
{ "data_hash", value->data.hash },
{ "secret", value->data.secret }
}));
}
if (!value->scans.empty()) {
auto files = QJsonArray();
for (const auto &scan : value->scans) {
files.append(GetJSONFromFile(scan));
}
object.insert("files", files);
}
if (_form.identitySelfieRequired && value->selfie) {
object.insert("selfie", GetJSONFromFile(*value->selfie));
}
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);
}
};
const auto scopes = ComputeScopes(this);
for (const auto &scope : scopes) {
const auto ready = ComputeScopeRowReadyString(scope);
if (ready.isEmpty()) {
_valueError.fire_copy(scope.fields);
}
addValue(scope.fields);
if (!scope.documents.empty()) {
for (const auto &document : scope.documents) {
if (!document->scans.empty()) {
addValue(document);
break;
}
}
}
}
auto json = QJsonObject();
json.insert("secure_data", secureData);
json.insert("payload", _request.payload);
return {
hashes,
QJsonDocument(json).toJson(QJsonDocument::Compact)
};
}
void FormController::submit() {
if (_submitRequestId) {
return;
}
const auto prepared = prepareFinalData();
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) {
const auto url = qthelp::url_append_query(
_request.callbackUrl,
"tg_passport=success");
UrlClickHandler::doOpen(url);
}).fail([=](const RPCError &error) {
_view->show(Box<InformBox>(
"Failed sending data :(\n" + error.type()));
}).send();
}
void FormController::submitPassword(const QString &password) {
Expects(!_password.salt.empty());
@ -231,19 +395,7 @@ bool FormController::validateValueSecrets(Value &value) {
return false;
}
}
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;
}
}
if (value.selfie) {
auto &file = *value.selfie;
const auto validateFileSecret = [&](File &file) {
file.secret = DecryptValueSecret(
file.encryptedSecret,
_secret,
@ -253,6 +405,15 @@ bool FormController::validateValueSecrets(Value &value) {
"Forgetting files and data :("));
return false;
}
return true;
};
for (auto &scan : value.scans) {
if (!validateFileSecret(scan)) {
return false;
}
}
if (value.selfie && !validateFileSecret(*value.selfie)) {
return false;
}
return true;
}
@ -273,31 +434,31 @@ void FormController::uploadScan(
not_null<const Value*> value,
QByteArray &&content) {
const auto nonconst = findValue(value);
auto fileIndex = int(nonconst->filesInEdit.size());
nonconst->filesInEdit.emplace_back(
auto scanIndex = int(nonconst->scansInEdit.size());
nonconst->scansInEdit.emplace_back(
nonconst,
File(),
nullptr);
auto &file = nonconst->filesInEdit.back();
encryptFile(file, std::move(content), [=](UploadScanData &&result) {
Expects(fileIndex >= 0 && fileIndex < nonconst->filesInEdit.size());
auto &scan = nonconst->scansInEdit.back();
encryptFile(scan, std::move(content), [=](UploadScanData &&result) {
Expects(scanIndex >= 0 && scanIndex < nonconst->scansInEdit.size());
uploadEncryptedFile(
nonconst->filesInEdit[fileIndex],
nonconst->scansInEdit[scanIndex],
std::move(result));
});
}
void FormController::deleteScan(
not_null<const Value*> value,
int fileIndex) {
scanDeleteRestore(value, fileIndex, true);
int scanIndex) {
scanDeleteRestore(value, scanIndex, true);
}
void FormController::restoreScan(
not_null<const Value*> value,
int fileIndex) {
scanDeleteRestore(value, fileIndex, false);
int scanIndex) {
scanDeleteRestore(value, scanIndex, false);
}
void FormController::uploadSelfie(
@ -371,14 +532,14 @@ void FormController::encryptFile(
void FormController::scanDeleteRestore(
not_null<const Value*> value,
int fileIndex,
int scanIndex,
bool deleted) {
Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size());
Expects(scanIndex >= 0 && scanIndex < value->scansInEdit.size());
const auto nonconst = findValue(value);
auto &file = nonconst->filesInEdit[fileIndex];
file.deleted = deleted;
_scanUpdated.fire(&file);
auto &scan = nonconst->scansInEdit[scanIndex];
scan.deleted = deleted;
_scanUpdated.fire(&scan);
}
void FormController::selfieDeleteRestore(
@ -387,9 +548,9 @@ void FormController::selfieDeleteRestore(
Expects(value->selfieInEdit.has_value());
const auto nonconst = findValue(value);
auto &file = *nonconst->selfieInEdit;
file.deleted = deleted;
_scanUpdated.fire(&file);
auto &scan = *nonconst->selfieInEdit;
scan.deleted = deleted;
_scanUpdated.fire(&scan);
}
void FormController::subscribeToUploader() {
@ -502,6 +663,11 @@ auto FormController::valueSaveFinished() const
return _valueSaveFinished.events();
}
auto FormController::valueError() const
-> rpl::producer<not_null<const Value*>> {
return _valueError.events();
}
auto FormController::verificationNeeded() const
-> rpl::producer<not_null<const Value*>> {
return _verificationNeeded.events();
@ -598,14 +764,14 @@ void FormController::startValueEdit(not_null<const Value*> value) {
if (savingValue(nonconst)) {
return;
}
for (auto &file : nonconst->files) {
loadFile(file);
for (auto &scan : nonconst->scans) {
loadFile(scan);
}
if (nonconst->selfie) {
loadFile(*nonconst->selfie);
}
nonconst->filesInEdit = ranges::view::all(
nonconst->files
nonconst->scansInEdit = ranges::view::all(
nonconst->scans
) | ranges::view::transform([=](const File &file) {
return EditFile(nonconst, file, nullptr);
}) | ranges::to_vector;
@ -729,7 +895,7 @@ void FormController::clearValueEdit(not_null<Value*> value) {
if (savingValue(value)) {
return;
}
value->filesInEdit.clear();
value->scansInEdit.clear();
value->selfieInEdit = base::none;
value->data.encryptedSecretInEdit.clear();
value->data.hashInEdit.clear();
@ -770,8 +936,8 @@ bool FormController::editValueChanged(
not_null<const Value*> value,
const ValueMap &data) const {
auto filesCount = 0;
for (const auto &file : value->filesInEdit) {
if (editFileChanged(file)) {
for (const auto &scan : value->scansInEdit) {
if (editFileChanged(scan)) {
return true;
}
}
@ -804,7 +970,7 @@ void FormController::saveValueEdit(
if (!editValueChanged(nonconst, data)) {
nonconst->saveRequestId = -1;
crl::on_main(this, [=] {
base::take(nonconst->filesInEdit);
base::take(nonconst->scansInEdit);
base::take(nonconst->selfieInEdit);
base::take(nonconst->data.encryptedSecretInEdit);
base::take(nonconst->data.hashInEdit);
@ -848,12 +1014,12 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
};
auto inputFiles = QVector<MTPInputSecureFile>();
inputFiles.reserve(value->filesInEdit.size());
for (const auto &file : value->filesInEdit) {
if (file.deleted) {
inputFiles.reserve(value->scansInEdit.size());
for (const auto &scan : value->scansInEdit) {
if (scan.deleted) {
continue;
}
inputFiles.push_back(inputFile(file));
inputFiles.push_back(inputFile(scan));
}
if (value->data.secret.empty()) {
@ -894,11 +1060,11 @@ void FormController::saveEncryptedValue(not_null<Value*> value) {
}
Unexpected("Value type in saveEncryptedValue().");
}();
const auto flags = ((value->filesInEdit.empty()
const auto flags = ((value->scansInEdit.empty()
&& value->data.parsedInEdit.fields.empty())
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_data)
| (value->filesInEdit.empty()
| (value->scansInEdit.empty()
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_files)
| ((value->selfieInEdit && !value->selfieInEdit->deleted)
@ -954,27 +1120,15 @@ void FormController::sendSaveRequest(
data,
MTP_long(_secretId)
)).done([=](const MTPSecureValue &result) {
Expects(result.type() == mtpc_secureValue);
value->saveRequestId = 0;
const auto filesInEdit = base::take(value->filesInEdit);
auto selfiesInEdit = std::vector<EditFile>();
auto filesInEdit = base::take(value->scansInEdit);
if (auto selfie = base::take(value->selfieInEdit)) {
selfiesInEdit.push_back(std::move(*selfie));
filesInEdit.push_back(std::move(*selfie));
}
const auto &data = result.c_secureValue();
value->files = data.has_files()
? parseFiles(data.vfiles.v, filesInEdit)
: std::vector<File>();
value->selfie = data.has_selfie()
? parseFile(data.vselfie, selfiesInEdit)
: base::none;
value->data.encryptedSecret = std::move(
value->data.encryptedSecretInEdit);
value->data.parsed = std::move(value->data.parsedInEdit);
value->data.hash = std::move(value->data.hashInEdit);
const auto editScreens = value->editScreens;
*value = parseValue(result, filesInEdit);
decryptValue(*value);
value->editScreens = editScreens;
_valueSaveFinished.fire_copy(value);
}).fail([=](const RPCError &error) {
@ -1167,12 +1321,15 @@ void FormController::generateSecret(bytes::const_span password) {
}
void FormController::requestForm() {
auto normalizedKey = _request.publicKey;
normalizedKey.replace("\r\n", "\n");
if (_request.payload.isEmpty()) {
_formRequestId = -1;
Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
return;
}
_formRequestId = request(MTPaccount_GetAuthorizationForm(
MTP_int(_request.botId),
MTP_string(_request.scope),
MTP_bytes(normalizedKey.toUtf8())
MTP_string(_request.publicKey)
)).done([=](const MTPaccount_AuthorizationForm &result) {
_formRequestId = 0;
formDone(result);
@ -1250,12 +1407,14 @@ void FormController::fillDownloadedFile(
}
auto FormController::parseValue(
const MTPSecureValue &value) const -> Value {
const MTPSecureValue &value,
const std::vector<EditFile> &editData) const -> Value {
Expects(value.type() == mtpc_secureValue);
const auto &data = value.c_secureValue();
const auto type = ConvertType(data.vtype);
auto result = Value(type);
result.submitHash = bytes::make_vector(data.vhash.v);
if (data.has_data()) {
Assert(data.vdata.type() == mtpc_secureData);
const auto &fields = data.vdata.c_secureData();
@ -1264,10 +1423,10 @@ auto FormController::parseValue(
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
}
if (data.has_files()) {
result.files = parseFiles(data.vfiles.v);
result.scans = parseFiles(data.vfiles.v, editData);
}
if (data.has_selfie()) {
result.selfie = parseFile(data.vselfie);
result.selfie = parseFile(data.vselfie, editData);
}
if (data.has_plain_data()) {
switch (data.vplain_data.type()) {
@ -1281,7 +1440,6 @@ auto FormController::parseValue(
} break;
}
}
// #TODO passport selfie
return result;
}
@ -1290,9 +1448,9 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
return (file.uploadData && file.uploadData->fullId == fullId);
};
for (auto &[type, value] : _form.values) {
for (auto &file : value.filesInEdit) {
if (found(file)) {
return &file;
for (auto &scan : value.scansInEdit) {
if (found(scan)) {
return &scan;
}
}
if (value.selfieInEdit && found(*value.selfieInEdit)) {
@ -1307,9 +1465,9 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* {
return (file.fields.dcId == key.dcId && file.fields.id == key.id);
};
for (auto &[type, value] : _form.values) {
for (auto &file : value.filesInEdit) {
if (found(file)) {
return &file;
for (auto &scan : value.scansInEdit) {
if (found(scan)) {
return &scan;
}
}
if (value.selfieInEdit && found(*value.selfieInEdit)) {
@ -1325,9 +1483,9 @@ auto FormController::findFile(const FileKey &key)
return (file.dcId == key.dcId) && (file.id == key.id);
};
for (auto &[type, value] : _form.values) {
for (auto &file : value.files) {
if (found(file)) {
return { &value, &file };
for (auto &scan : value.scans) {
if (found(scan)) {
return { &value, &scan };
}
}
if (value.selfie && found(*value.selfie)) {

View file

@ -31,12 +31,14 @@ struct FormRequest {
UserId botId,
const QString &scope,
const QString &callbackUrl,
const QString &publicKey);
const QString &publicKey,
const QString &payload);
UserId botId;
QString scope;
QString callbackUrl;
QString publicKey;
QString payload;
};
@ -142,11 +144,12 @@ struct Value {
Type type;
ValueData data;
std::vector<File> files;
std::vector<EditFile> filesInEdit;
std::vector<File> scans;
std::vector<EditFile> scansInEdit;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
Verification verification;
bytes::vector submitHash;
int editScreens = 0;
mtpRequestId saveRequestId = 0;
@ -204,7 +207,7 @@ public:
void show();
UserData *bot() const;
QString privacyPolicyUrl() const;
void submit();
void submitPassword(const QString &password);
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
@ -223,6 +226,7 @@ public:
rpl::producer<not_null<const EditFile*>> scanUpdated() const;
rpl::producer<not_null<const Value*>> valueSaveFinished() const;
rpl::producer<not_null<const Value*>> valueError() const;
rpl::producer<not_null<const Value*>> verificationNeeded() const;
rpl::producer<not_null<const Value*>> verificationUpdate() const;
void verify(not_null<const Value*> value, const QString &code);
@ -244,6 +248,10 @@ public:
~FormController();
private:
struct FinalData {
QVector<MTPSecureValueHash> hashes;
QByteArray credentials;
};
EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key);
std::pair<Value*, File*> findFile(const FileKey &key);
@ -256,13 +264,15 @@ private:
void formFail(const RPCError &error);
void parseForm(const MTPaccount_AuthorizationForm &result);
void showForm();
Value parseValue(const MTPSecureValue &value) const;
Value parseValue(
const MTPSecureValue &value,
const std::vector<EditFile> &editData = {}) const;
std::vector<File> parseFiles(
const QVector<MTPSecureFile> &data,
const std::vector<EditFile> &editData = {}) const;
const std::vector<EditFile> &editData) const;
base::optional<File> parseFile(
const MTPSecureFile &data,
const std::vector<EditFile> &editData = {}) const;
const std::vector<EditFile> &editData) const;
void fillDownloadedFile(
File &destination,
const std::vector<EditFile> &source) const;
@ -330,6 +340,7 @@ private:
void sendSaveRequest(
not_null<Value*> value,
const MTPInputSecureValue &data);
FinalData prepareFinalData() const;
not_null<Window::Controller*> _controller;
FormRequest _request;
@ -346,6 +357,7 @@ private:
rpl::event_stream<not_null<const EditFile*>> _scanUpdated;
rpl::event_stream<not_null<const Value*>> _valueSaveFinished;
rpl::event_stream<not_null<const Value*>> _valueError;
rpl::event_stream<not_null<const Value*>> _verificationNeeded;
rpl::event_stream<not_null<const Value*>> _verificationUpdate;
@ -355,6 +367,7 @@ private:
mtpRequestId _saveSecretRequestId = 0;
rpl::event_stream<> _secretReady;
rpl::event_stream<QString> _passwordError;
mtpRequestId _submitRequestId = 0;
rpl::lifetime _uploaderSubscriptions;
rpl::lifetime _lifetime;

View file

@ -8,6 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_form_view_controller.h"
#include "passport/passport_form_controller.h"
#include "passport/passport_panel_edit_document.h"
#include "passport/passport_panel_edit_contact.h"
#include "passport/passport_panel_controller.h"
#include "lang/lang_keys.h"
namespace Passport {
namespace {
@ -57,7 +61,8 @@ Scope::Scope(Type type, not_null<const Value*> fields)
, fields(fields) {
}
std::vector<Scope> ComputeScopes(not_null<FormController*> controller) {
std::vector<Scope> ComputeScopes(
not_null<const FormController*> controller) {
auto scopes = std::map<Scope::Type, Scope>();
const auto &form = controller->form();
const auto findValue = [&](const Value::Type type) {
@ -74,15 +79,15 @@ std::vector<Scope> ComputeScopes(not_null<FormController*> controller) {
i->second.selfieRequired = (scopeType == Scope::Type::Identity)
&& form.identitySelfieRequired;
const auto alreadyIt = ranges::find(
i->second.files,
i->second.documents,
type,
[](not_null<const Value*> value) { return value->type; });
if (alreadyIt != end(i->second.files)) {
if (alreadyIt != end(i->second.documents)) {
LOG(("API Error: Value type %1 multiple times in request."
).arg(int(type)));
continue;
} else if (type != fieldsType) {
i->second.files.push_back(findValue(type));
i->second.documents.push_back(findValue(type));
}
}
auto result = std::vector<Scope>();
@ -93,4 +98,168 @@ std::vector<Scope> ComputeScopes(not_null<FormController*> controller) {
return result;
}
QString ComputeScopeRowReadyString(const Scope &scope) {
switch (scope.type) {
case Scope::Type::Identity:
case Scope::Type::Address: {
auto list = QStringList();
const auto &fields = scope.fields->data.parsed.fields;
const auto document = [&]() -> const Value* {
for (const auto &document : scope.documents) {
if (!document->scans.empty()) {
return document;
}
}
return nullptr;
}();
if (document && scope.documents.size() > 1) {
list.push_back([&] {
switch (document->type) {
case Value::Type::Passport:
return lang(lng_passport_identity_passport);
case Value::Type::DriverLicense:
return lang(lng_passport_identity_license);
case Value::Type::IdentityCard:
return lang(lng_passport_identity_card);
case Value::Type::BankStatement:
return lang(lng_passport_address_statement);
case Value::Type::UtilityBill:
return lang(lng_passport_address_bill);
case Value::Type::RentalAgreement:
return lang(lng_passport_address_agreement);
default: Unexpected("Files type in ComputeScopeRowReadyString.");
}
}());
}
if (document
&& (document->scans.empty()
|| (scope.selfieRequired && !document->selfie))) {
return QString();
}
const auto scheme = GetDocumentScheme(scope.type);
for (const auto &row : scheme.rows) {
const auto format = row.format;
if (row.valueClass == EditDocumentScheme::ValueClass::Fields) {
const auto i = fields.find(row.key);
if (i == end(fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
return QString();
}
list.push_back(format ? format(i->second) : i->second);
} else if (!document) {
return QString();
} else {
const auto i = document->data.parsed.fields.find(row.key);
if (i == end(document->data.parsed.fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
return QString();
}
list.push_back(i->second);
}
}
return list.join(", ");
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
const auto format = GetContactScheme(scope.type).format;
const auto &fields = scope.fields->data.parsed.fields;
const auto i = fields.find("value");
return (i != end(fields))
? (format ? format(i->second) : i->second)
: QString();
} break;
}
Unexpected("Scope type in ComputeScopeRowReadyString.");
}
ScopeRow ComputeScopeRow(const Scope &scope) {
switch (scope.type) {
case Scope::Type::Identity:
if (scope.documents.empty()) {
return {
lang(lng_passport_personal_details),
lang(lng_passport_personal_details_enter),
ComputeScopeRowReadyString(scope)
};
} else if (scope.documents.size() == 1) {
switch (scope.documents.front()->type) {
case Value::Type::Passport:
return {
lang(lng_passport_identity_passport),
lang(lng_passport_identity_passport_upload),
ComputeScopeRowReadyString(scope)
};
case Value::Type::IdentityCard:
return {
lang(lng_passport_identity_card),
lang(lng_passport_identity_card_upload),
ComputeScopeRowReadyString(scope)
};
case Value::Type::DriverLicense:
return {
lang(lng_passport_identity_license),
lang(lng_passport_identity_license_upload),
ComputeScopeRowReadyString(scope)
};
default: Unexpected("Identity type in ComputeScopeRow.");
}
}
return {
lang(lng_passport_identity_title),
lang(lng_passport_identity_description),
ComputeScopeRowReadyString(scope)
};
case Scope::Type::Address:
if (scope.documents.empty()) {
return {
lang(lng_passport_address),
lang(lng_passport_address_enter),
ComputeScopeRowReadyString(scope)
};
} else if (scope.documents.size() == 1) {
switch (scope.documents.front()->type) {
case Value::Type::BankStatement:
return {
lang(lng_passport_address_statement),
lang(lng_passport_address_statement_upload),
ComputeScopeRowReadyString(scope)
};
case Value::Type::UtilityBill:
return {
lang(lng_passport_address_bill),
lang(lng_passport_address_bill_upload),
ComputeScopeRowReadyString(scope)
};
case Value::Type::RentalAgreement:
return {
lang(lng_passport_address_agreement),
lang(lng_passport_address_agreement_upload),
ComputeScopeRowReadyString(scope)
};
default: Unexpected("Address type in ComputeScopeRow.");
}
}
return {
lang(lng_passport_address_title),
lang(lng_passport_address_description),
ComputeScopeRowReadyString(scope)
};
case Scope::Type::Phone:
return {
lang(lng_passport_phone_title),
lang(lng_passport_phone_description),
ComputeScopeRowReadyString(scope)
};
case Scope::Type::Email:
return {
lang(lng_passport_email_title),
lang(lng_passport_email_description),
ComputeScopeRowReadyString(scope)
};
default: Unexpected("Scope type in ComputeScopeRow.");
}
}
} // namespace Passport

View file

@ -22,11 +22,20 @@ struct Scope {
Type type;
not_null<const Value*> fields;
std::vector<not_null<const Value*>> files;
std::vector<not_null<const Value*>> documents;
bool selfieRequired = false;
};
std::vector<Scope> ComputeScopes(not_null<FormController*> controller);
struct ScopeRow {
QString title;
QString description;
QString ready;
};
std::vector<Scope> ComputeScopes(
not_null<const FormController*> controller);
QString ComputeScopeRowReadyString(const Scope &scope);
ScopeRow ComputeScopeRow(const Scope &scope);
class ViewController {
public:

View file

@ -18,13 +18,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "layout.h"
namespace Passport {
namespace {
PanelEditDocument::Scheme GetDocumentScheme(
EditDocumentScheme GetDocumentScheme(
Scope::Type type,
base::optional<Value::Type> scansType = base::none) {
using Scheme = PanelEditDocument::Scheme;
base::optional<Value::Type> scansType) {
using Scheme = EditDocumentScheme;
using ValueClass = Scheme::ValueClass;
const auto DontFormat = nullptr;
const auto CountryFormat = [](const QString &value) {
const auto result = CountrySelectBox::NameByISO(value);
@ -78,7 +77,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
}
result.rows = {
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("first_name"),
lang(lng_passport_first_name),
@ -86,7 +85,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("last_name"),
lang(lng_passport_last_name),
@ -94,7 +93,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Date,
qsl("birth_date"),
lang(lng_passport_birth_date),
@ -102,7 +101,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Gender,
qsl("gender"),
lang(lng_passport_gender),
@ -110,7 +109,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
GenderFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Country,
qsl("country_code"),
lang(lng_passport_country),
@ -118,7 +117,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
CountryFormat,
},
{
Scheme::ValueType::Scans,
ValueClass::Scans,
PanelDetailsType::Text,
qsl("document_no"),
lang(lng_passport_document_number),
@ -126,7 +125,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Scans,
ValueClass::Scans,
PanelDetailsType::Date,
qsl("expiry_date"),
lang(lng_passport_expiry_date),
@ -157,7 +156,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
}
result.rows = {
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("street_line1"),
lang(lng_passport_street),
@ -165,7 +164,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("street_line2"),
lang(lng_passport_street),
@ -173,7 +172,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("city"),
lang(lng_passport_city),
@ -181,7 +180,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("state"),
lang(lng_passport_state),
@ -189,7 +188,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
DontFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Country,
qsl("country_code"),
lang(lng_passport_country),
@ -197,7 +196,7 @@ PanelEditDocument::Scheme GetDocumentScheme(
CountryFormat,
},
{
Scheme::ValueType::Fields,
ValueClass::Fields,
PanelDetailsType::Text,
qsl("post_code"),
lang(lng_passport_postcode),
@ -211,11 +210,13 @@ PanelEditDocument::Scheme GetDocumentScheme(
Unexpected("Type in GetDocumentScheme().");
}
PanelEditContact::Scheme GetContactScheme(Scope::Type type) {
using Scheme = PanelEditContact::Scheme;
EditContactScheme GetContactScheme(Scope::Type type) {
using Scheme = EditContactScheme;
using ValueType = Scheme::ValueType;
switch (type) {
case Scope::Type::Phone: {
auto result = Scheme(Scheme::ValueType::Phone);
auto result = Scheme(ValueType::Phone);
result.aboutExisting = lang(lng_passport_use_existing_phone);
result.newHeader = lang(lng_passport_new_phone);
result.aboutNew = lang(lng_passport_new_phone_code);
@ -234,7 +235,7 @@ PanelEditContact::Scheme GetContactScheme(Scope::Type type) {
} break;
case Scope::Type::Email: {
auto result = Scheme(Scheme::ValueType::Text);
auto result = Scheme(ValueType::Text);
result.aboutExisting = lang(lng_passport_use_existing_email);
result.newHeader = lang(lng_passport_new_email);
result.newPlaceholder = langFactory(lng_passport_email_title);
@ -253,8 +254,6 @@ PanelEditContact::Scheme GetContactScheme(Scope::Type type) {
Unexpected("Type in GetContactScheme().");
}
} // namespace
BoxPointer::BoxPointer(QPointer<BoxContent> value)
: _value(value) {
}
@ -323,158 +322,6 @@ QString PanelController::privacyPolicyUrl() const {
return _form->privacyPolicyUrl();
}
auto PanelController::collectRowInfo(const Scope &scope) const -> Row {
switch (scope.type) {
case Scope::Type::Identity:
if (scope.files.empty()) {
return {
lang(lng_passport_personal_details),
lang(lng_passport_personal_details_enter)
};
} else if (scope.files.size() == 1) {
switch (scope.files.front()->type) {
case Value::Type::Passport:
return {
lang(lng_passport_identity_passport),
lang(lng_passport_identity_passport_upload)
};
case Value::Type::IdentityCard:
return {
lang(lng_passport_identity_card),
lang(lng_passport_identity_card_upload)
};
case Value::Type::DriverLicense:
return {
lang(lng_passport_identity_license),
lang(lng_passport_identity_license_upload)
};
default: Unexpected("Identity type in collectRowInfo.");
}
}
return {
lang(lng_passport_identity_title),
lang(lng_passport_identity_description)
};
case Scope::Type::Address:
if (scope.files.empty()) {
return {
lang(lng_passport_address),
lang(lng_passport_address_enter)
};
} else if (scope.files.size() == 1) {
switch (scope.files.front()->type) {
case Value::Type::BankStatement:
return {
lang(lng_passport_address_statement),
lang(lng_passport_address_statement_upload)
};
case Value::Type::UtilityBill:
return {
lang(lng_passport_address_bill),
lang(lng_passport_address_bill_upload)
};
case Value::Type::RentalAgreement:
return {
lang(lng_passport_address_agreement),
lang(lng_passport_address_agreement_upload)
};
default: Unexpected("Address type in collectRowInfo.");
}
}
return {
lang(lng_passport_address_title),
lang(lng_passport_address_description)
};
case Scope::Type::Phone:
return {
lang(lng_passport_phone_title),
lang(lng_passport_phone_description)
};
case Scope::Type::Email:
return {
lang(lng_passport_email_title),
lang(lng_passport_email_description)
};
default: Unexpected("Scope type in collectRowInfo.");
}
}
QString PanelController::collectRowReadyString(const Scope &scope) const {
switch (scope.type) {
case Scope::Type::Identity:
case Scope::Type::Address: {
auto list = QStringList();
const auto &fields = scope.fields->data.parsed.fields;
const auto files = [&]() -> const Value* {
for (const auto &files : scope.files) {
if (!files->files.empty()) {
return files;
}
}
return nullptr;
}();
if (files && scope.files.size() > 1) {
list.push_back([&] {
switch (files->type) {
case Value::Type::Passport:
return lang(lng_passport_identity_passport);
case Value::Type::DriverLicense:
return lang(lng_passport_identity_license);
case Value::Type::IdentityCard:
return lang(lng_passport_identity_card);
case Value::Type::BankStatement:
return lang(lng_passport_address_statement);
case Value::Type::UtilityBill:
return lang(lng_passport_address_bill);
case Value::Type::RentalAgreement:
return lang(lng_passport_address_agreement);
default: Unexpected("Files type in collectRowReadyString.");
}
}());
}
if (files
&& (files->files.empty()
|| (scope.selfieRequired && !files->selfie))) {
return QString();
}
const auto scheme = GetDocumentScheme(scope.type);
for (const auto &row : scheme.rows) {
const auto format = row.format;
if (row.type == PanelEditDocument::Scheme::ValueType::Fields) {
const auto i = fields.find(row.key);
if (i == end(fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
return QString();
}
list.push_back(format ? format(i->second) : i->second);
} else if (!files) {
return QString();
} else {
const auto i = files->data.parsed.fields.find(row.key);
if (i == end(files->data.parsed.fields)) {
return QString();
} else if (row.validate && !row.validate(i->second)) {
return QString();
}
list.push_back(i->second);
}
}
return list.join(", ");
} break;
case Scope::Type::Phone:
case Scope::Type::Email: {
const auto format = GetContactScheme(scope.type).format;
const auto &fields = scope.fields->data.parsed.fields;
const auto i = fields.find("value");
return (i != end(fields))
? (format ? format(i->second) : i->second)
: QString();
} break;
}
Unexpected("Scope type in collectRowReadyString.");
}
void PanelController::fillRows(
base::lambda<void(
QString title,
@ -484,15 +331,18 @@ void PanelController::fillRows(
_scopes = ComputeScopes(_form);
}
for (const auto &scope : _scopes) {
const auto row = collectRowInfo(scope);
const auto ready = collectRowReadyString(scope);
const auto row = ComputeScopeRow(scope);
callback(
row.title,
ready.isEmpty() ? row.description : ready,
!ready.isEmpty());
row.ready.isEmpty() ? row.description : row.ready,
!row.ready.isEmpty());
}
}
void PanelController::submitForm() {
_form->submit();
}
void PanelController::submitPassword(const QString &password) {
_form->submitPassword(password);
}
@ -515,71 +365,71 @@ QString PanelController::defaultPhoneNumber() const {
void PanelController::uploadScan(QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
_form->uploadScan(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
std::move(content));
}
void PanelController::deleteScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
_form->deleteScan(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
fileIndex);
}
void PanelController::restoreScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
_form->restoreScan(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
fileIndex);
}
void PanelController::uploadSelfie(QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editScope->selfieRequired);
_form->uploadSelfie(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
std::move(content));
}
void PanelController::deleteSelfie() {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editScope->selfieRequired);
_form->deleteSelfie(
_editScope->files[_editScopeFilesIndex]);
_editScope->documents[_editDocumentIndex]);
}
void PanelController::restoreSelfie() {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0
&& _editScopeFilesIndex < _editScope->files.size());
Expects(_editDocumentIndex >= 0
&& _editDocumentIndex < _editScope->documents.size());
Expects(_editScope->selfieRequired);
_form->restoreSelfie(
_editScope->files[_editScopeFilesIndex]);
_editScope->documents[_editDocumentIndex]);
}
rpl::producer<ScanInfo> PanelController::scanUpdated() const {
return _form->scanUpdated(
) | rpl::filter([=](not_null<const EditFile*> file) {
return (_editScope != nullptr)
&& (_editScopeFilesIndex >= 0)
&& (file->value == _editScope->files[_editScopeFilesIndex]);
&& (_editDocumentIndex >= 0)
&& (file->value == _editScope->documents[_editDocumentIndex]);
}) | rpl::map([=](not_null<const EditFile*> file) {
return collectScanInfo(*file);
});
@ -587,7 +437,7 @@ rpl::producer<ScanInfo> PanelController::scanUpdated() const {
ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0);
Expects(_editDocumentIndex >= 0);
const auto status = [&] {
if (file.fields.accessHash) {
@ -618,10 +468,10 @@ ScanInfo PanelController::collectScanInfo(const EditFile &file) const {
return formatDownloadText(0, file.fields.size);
}
}();
const auto &files = _editScope->files;
auto isSelfie = (file.value == files[_editScopeFilesIndex])
&& (files[_editScopeFilesIndex]->selfieInEdit.has_value())
&& (&file == &*files[_editScopeFilesIndex]->selfieInEdit);
const auto &documents = _editScope->documents;
auto isSelfie = (file.value == documents[_editDocumentIndex])
&& (documents[_editDocumentIndex]->selfieInEdit.has_value())
&& (&file == &*documents[_editDocumentIndex]->selfieInEdit);
return {
FileKey{ file.fields.id, file.fields.dcId },
status,
@ -664,7 +514,7 @@ void PanelController::ensurePanelCreated() {
int PanelController::findNonEmptyIndex(
const std::vector<not_null<const Value*>> &files) const {
const auto i = ranges::find_if(files, [](not_null<const Value*> file) {
return !file->files.empty();
return !file->scans.empty();
});
if (i != end(files)) {
return (i - begin(files));
@ -677,13 +527,14 @@ void PanelController::editScope(int index) {
Expects(_panel != nullptr);
Expects(index >= 0 && index < _scopes.size());
if (_scopes[index].files.empty()) {
if (_scopes[index].documents.empty()) {
editScope(index, -1);
} else {
const auto filesIndex = findNonEmptyIndex(_scopes[index].files);
if (filesIndex >= 0) {
editScope(index, filesIndex);
} else if (_scopes[index].files.size() > 1) {
const auto documentIndex = findNonEmptyIndex(
_scopes[index].documents);
if (documentIndex >= 0) {
editScope(index, documentIndex);
} else if (_scopes[index].documents.size() > 1) {
requestScopeFilesType(index);
} else {
editWithUpload(index, 0);
@ -696,14 +547,14 @@ void PanelController::requestScopeFilesType(int index) {
Expects(index >= 0 && index < _scopes.size());
const auto type = _scopes[index].type;
_scopeFilesTypeBox = [&] {
_scopeDocumentTypeBox = [&] {
if (type == Scope::Type::Identity) {
return show(RequestIdentityType(
[=](int filesIndex) {
editWithUpload(index, filesIndex);
[=](int documentIndex) {
editWithUpload(index, documentIndex);
},
ranges::view::all(
_scopes[index].files
_scopes[index].documents
) | ranges::view::transform([](auto value) {
return value->type;
}) | ranges::view::transform([](Value::Type type) {
@ -720,11 +571,11 @@ void PanelController::requestScopeFilesType(int index) {
}) | ranges::to_vector));
} else if (type == Scope::Type::Address) {
return show(RequestAddressType(
[=](int filesIndex) {
editWithUpload(index, filesIndex);
[=](int documentIndex) {
editWithUpload(index, documentIndex);
},
ranges::view::all(
_scopes[index].files
_scopes[index].documents
) | ranges::view::transform([](auto value) {
return value->type;
}) | ranges::view::transform([](Value::Type type) {
@ -745,51 +596,53 @@ void PanelController::requestScopeFilesType(int index) {
}();
}
void PanelController::editWithUpload(int index, int filesIndex) {
void PanelController::editWithUpload(int index, int documentIndex) {
Expects(_panel != nullptr);
Expects(index >= 0 && index < _scopes.size());
Expects(filesIndex >= 0 && filesIndex < _scopes[index].files.size());
Expects(documentIndex >= 0
&& documentIndex < _scopes[index].documents.size());
EditScans::ChooseScan(
base::lambda_guarded(_panel.get(),
[=](QByteArray &&content) {
base::take(_scopeFilesTypeBox);
editScope(index, filesIndex);
base::take(_scopeDocumentTypeBox);
editScope(index, documentIndex);
uploadScan(std::move(content));
}));
}
void PanelController::editScope(int index, int filesIndex) {
void PanelController::editScope(int index, int documentIndex) {
Expects(_panel != nullptr);
Expects(index >= 0 && index < _scopes.size());
Expects((filesIndex < 0)
|| (filesIndex >= 0 && filesIndex < _scopes[index].files.size()));
Expects((documentIndex < 0)
|| (documentIndex >= 0
&& documentIndex < _scopes[index].documents.size()));
_editScope = &_scopes[index];
_editScopeFilesIndex = filesIndex;
_editDocumentIndex = documentIndex;
_form->startValueEdit(_editScope->fields);
if (_editScopeFilesIndex >= 0) {
_form->startValueEdit(_editScope->files[_editScopeFilesIndex]);
if (_editDocumentIndex >= 0) {
_form->startValueEdit(_editScope->documents[_editDocumentIndex]);
}
auto content = [&]() -> object_ptr<Ui::RpWidget> {
switch (_editScope->type) {
case Scope::Type::Identity:
case Scope::Type::Address: {
const auto &files = _editScope->files;
auto result = (_editScopeFilesIndex >= 0)
const auto &documents = _editScope->documents;
auto result = (_editDocumentIndex >= 0)
? object_ptr<PanelEditDocument>(
_panel.get(),
this,
GetDocumentScheme(
_editScope->type,
files[_editScopeFilesIndex]->type),
documents[_editDocumentIndex]->type),
_editScope->fields->data.parsedInEdit,
files[_editScopeFilesIndex]->data.parsedInEdit,
valueFiles(*files[_editScopeFilesIndex]),
documents[_editDocumentIndex]->data.parsedInEdit,
valueFiles(*documents[_editDocumentIndex]),
(_editScope->selfieRequired
? valueSelfie(*files[_editScopeFilesIndex])
? valueSelfie(*documents[_editDocumentIndex])
: nullptr))
: object_ptr<PanelEditDocument>(
_panel.get(),
@ -850,8 +703,8 @@ void PanelController::processValueSaveFinished(
}
const auto value1 = _editScope->fields;
const auto value2 = (_editScopeFilesIndex >= 0)
? _editScope->files[_editScopeFilesIndex].get()
const auto value2 = (_editDocumentIndex >= 0)
? _editScope->documents[_editDocumentIndex].get()
: nullptr;
if (value == value1 || value == value2) {
if (!_form->savingValue(value1)
@ -925,8 +778,8 @@ void PanelController::processVerificationNeeded(
std::vector<ScanInfo> PanelController::valueFiles(
const Value &value) const {
auto result = std::vector<ScanInfo>();
for (const auto &file : value.filesInEdit) {
result.push_back(collectScanInfo(file));
for (const auto &scan : value.scansInEdit) {
result.push_back(collectScanInfo(scan));
}
return result;
}
@ -943,9 +796,9 @@ std::unique_ptr<ScanInfo> PanelController::valueSelfie(
void PanelController::cancelValueEdit() {
if (const auto scope = base::take(_editScope)) {
_form->cancelValueEdit(scope->fields);
const auto index = std::exchange(_editScopeFilesIndex, -1);
const auto index = std::exchange(_editDocumentIndex, -1);
if (index >= 0) {
_form->cancelValueEdit(scope->files[index]);
_form->cancelValueEdit(scope->documents[index]);
}
}
}
@ -955,9 +808,9 @@ void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
Expects(_editScope != nullptr);
_form->saveValueEdit(_editScope->fields, std::move(data));
if (_editScopeFilesIndex >= 0) {
if (_editDocumentIndex >= 0) {
_form->saveValueEdit(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
std::move(filesData));
} else {
Assert(filesData.fields.empty());
@ -971,9 +824,9 @@ bool PanelController::editScopeChanged(
if (_form->editValueChanged(_editScope->fields, data)) {
return true;
} else if (_editScopeFilesIndex >= 0) {
} else if (_editDocumentIndex >= 0) {
return _form->editValueChanged(
_editScope->files[_editScopeFilesIndex],
_editScope->documents[_editDocumentIndex],
filesData);
}
return false;

View file

@ -15,6 +15,14 @@ namespace Passport {
class FormController;
class Panel;
struct EditDocumentScheme;
struct EditContactScheme;
EditDocumentScheme GetDocumentScheme(
Scope::Type type,
base::optional<Value::Type> scansType = base::none);
EditContactScheme GetContactScheme(Scope::Type type);
struct ScanInfo {
FileKey key;
QString status;
@ -47,7 +55,7 @@ public:
not_null<UserData*> bot() const;
QString privacyPolicyUrl() const;
void submitForm();
void submitPassword(const QString &password);
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
@ -87,14 +95,10 @@ public:
rpl::lifetime &lifetime();
private:
struct Row {
QString title;
QString description;
};
void ensurePanelCreated();
void editScope(int index, int filesIndex);
void editWithUpload(int index, int filesIndex);
void editScope(int index, int documentIndex);
void editWithUpload(int index, int documentIndex);
int findNonEmptyIndex(
const std::vector<not_null<const Value*>> &files) const;
void requestScopeFilesType(int index);
@ -104,8 +108,6 @@ private:
void processValueSaveFinished(not_null<const Value*> value);
void processVerificationNeeded(not_null<const Value*> value);
Row collectRowInfo(const Scope &scope) const;
QString collectRowReadyString(const Scope &scope) const;
ScanInfo collectScanInfo(const EditFile &file) const;
QString getDefaultContactValue(Scope::Type type) const;
@ -116,8 +118,8 @@ private:
base::lambda<bool()> _panelHasUnsavedChanges;
BoxPointer _confirmForgetChangesBox;
Scope *_editScope = nullptr;
int _editScopeFilesIndex = -1;
BoxPointer _scopeFilesTypeBox;
int _editDocumentIndex = -1;
BoxPointer _scopeDocumentTypeBox;
std::map<not_null<const Value*>, BoxPointer> _verificationBoxes;
rpl::lifetime _lifetime;

View file

@ -159,7 +159,7 @@ void VerifyBox::prepare() {
} // namespace
PanelEditContact::Scheme::Scheme(ValueType type) : type(type) {
EditContactScheme::EditContactScheme(ValueType type) : type(type) {
}
PanelEditContact::PanelEditContact(

View file

@ -20,26 +20,28 @@ namespace Passport {
class PanelController;
struct EditContactScheme {
enum class ValueType {
Phone,
Text,
};
explicit EditContactScheme(ValueType type);
ValueType type;
QString aboutExisting;
QString newHeader;
base::lambda<QString()> newPlaceholder;
QString aboutNew;
base::lambda<bool(const QString &value)> validate;
base::lambda<QString(const QString &value)> format;
base::lambda<QString(const QString &value)> postprocess;
};
class PanelEditContact : public Ui::RpWidget {
public:
struct Scheme {
enum class ValueType {
Phone,
Text,
};
explicit Scheme(ValueType type);
ValueType type;
QString aboutExisting;
QString newHeader;
base::lambda<QString()> newPlaceholder;
QString aboutNew;
base::lambda<bool(const QString &value)> validate;
base::lambda<QString(const QString &value)> format;
base::lambda<QString(const QString &value)> postprocess;
};
using Scheme = EditContactScheme;
PanelEditContact(
QWidget *parent,

View file

@ -234,7 +234,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
const auto &row = _scheme.rows[i];
auto fields = (row.type == Scheme::ValueType::Fields)
auto fields = (row.valueClass == Scheme::ValueClass::Fields)
? &data
: scanData;
if (!fields) {
@ -289,7 +289,7 @@ PanelEditDocument::Result PanelEditDocument::collect() const {
auto result = Result();
for (const auto [i, field] : _details) {
const auto &row = _scheme.rows[i];
auto &fields = (row.type == Scheme::ValueType::Fields)
auto &fields = (row.valueClass == Scheme::ValueClass::Fields)
? result.data
: result.filesData;
fields.fields[row.key] = field->valueCurrent();

View file

@ -26,26 +26,28 @@ class EditScans;
class PanelDetailsRow;
enum class PanelDetailsType;
struct EditDocumentScheme {
enum class ValueClass {
Fields,
Scans,
};
struct Row {
ValueClass valueClass = ValueClass::Fields;
PanelDetailsType inputType = PanelDetailsType();
QString key;
QString label;
base::lambda<bool(const QString &value)> validate;
base::lambda<QString(const QString &value)> format;
};
std::vector<Row> rows;
QString rowsHeader;
QString scansHeader;
};
class PanelEditDocument : public Ui::RpWidget {
public:
struct Scheme {
enum class ValueType {
Fields,
Scans,
};
struct Row {
ValueType type = ValueType::Fields;
PanelDetailsType inputType = PanelDetailsType();
QString key;
QString label;
base::lambda<bool(const QString &value)> validate;
base::lambda<QString(const QString &value)> format;
};
std::vector<Row> rows;
QString rowsHeader;
QString scansHeader;
};
using Scheme = EditDocumentScheme;
PanelEditDocument(
QWidget *parent,

View file

@ -145,6 +145,10 @@ PanelForm::PanelForm(
void PanelForm::setupControls() {
const auto inner = setupContent();
_submit->addClickHandler([=] {
_controller->submitForm();
});
using namespace rpl::mappers;
_topShadow->toggleOn(