Update API scheme.

This commit is contained in:
John Preston 2018-04-03 22:24:31 +04:00
parent 5cfead762d
commit f0b7ff24b1
13 changed files with 527 additions and 334 deletions

View File

@ -1521,6 +1521,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_email_title" = "Email";
"lng_passport_email_description" = "Enter your email address";
"lng_passport_accept_allow" = "You accept {policy} and allow their {bot} to send messages to you.";
"lng_passport_allow" = "You allow {bot} to send messages to you.";
"lng_passport_policy" = "{bot} privacy policy";
"lng_passport_authorize" = "Authorize";
"lng_passport_form_error" = "Could not get authorization form.";

View File

@ -975,30 +975,29 @@ secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_ha
secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
secureValueVerified#2c8e61e2 date:int = SecureValueVerified;
securePlainPhone#7d6099dd phone:string = SecurePlainData;
securePlainEmail#21ec5a5f email:string = SecurePlainData;
secureValueTypeIdentity#37da58ca = SecureValueType;
secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
secureValueTypePassport#3dac6a00 = SecureValueType;
secureValueTypeDriverLicense#6e425c4 = SecureValueType;
secureValueTypeIdentityCard#a0d0744b = SecureValueType;
secureValueTypeAddress#cbe31e26 = SecureValueType;
secureValueTypeUtilityBill#fc36954e = SecureValueType;
secureValueTypeBankStatement#89137c0d = SecureValueType;
secureValueTypeRentalAgreement#8b883488 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValueIdentity#15fe72a2 flags:# data:SecureData files:Vector<SecureFile> verified:flags.0?SecureValueVerified = SecureValue;
secureValueAddress#88109b79 flags:# data:SecureData files:Vector<SecureFile> verified:flags.0?SecureValueVerified = SecureValue;
secureValuePhone#e39470bf flags:# phone:string verified:flags.0?SecureValueVerified = SecureValue;
secureValueEmail#35d804cd flags:# email:string verified:flags.0?SecureValueVerified = SecureValue;
secureValue#ec4134c8 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector<SecureFile> plain_data:flags.2?SecurePlainData selfie:flags.3?SecureFile hash:bytes = SecureValue;
inputSecureValueIdentity#94fa65b data:SecureData files:Vector<InputSecureFile> = InputSecureValue;
inputSecureValueAddress#96689355 data:SecureData files:Vector<InputSecureFile> = InputSecureValue;
inputSecureValuePhone#9d623d96 phone:string = InputSecureValue;
inputSecureValueEmail#9e885359 email:string = InputSecureValue;
inputSecureValue#c0da30f0 flags:# type:SecureValueType data:flags.0?SecureData files:flags.1?Vector<InputSecureFile> plain_data:flags.2?SecurePlainData selfie:flags.3?InputSecureFile = InputSecureValue;
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
secureValueSaved#c9147049 files:Vector<SecureFile> = SecureValueSaved;
secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
account.authorizationForm#4cace8c4 required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> = account.AuthorizationForm;
account.authorizationForm#b9d3d1f0 flags:# selfie_required:flags.1?true required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode;
@ -1057,8 +1056,8 @@ account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPass
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
account.resetWebAuthorizations#682d2594 = Bool;
account.getSecureValue#d97e77cb user_id:InputUser types:Vector<SecureValueType> = Vector<SecureValue>;
account.saveSecureValue#78969d0b value:InputSecureValue secure_secret_id:long = SecureValueSaved;
account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue;
account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm;
account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;

View File

@ -29,6 +29,23 @@ QImage ReadImage(bytes::const_span buffer) {
buffer.size()));
}
Value::Type ConvertType(const MTPSecureValueType &type) {
using Type = Value::Type;
switch (type.type()) {
case mtpc_secureValueTypePersonalDetails: return Type::PersonalDetails;
case mtpc_secureValueTypePassport: return Type::Passport;
case mtpc_secureValueTypeDriverLicense: return Type::DriverLicense;
case mtpc_secureValueTypeIdentityCard: return Type::IdentityCard;
case mtpc_secureValueTypeAddress: return Type::Address;
case mtpc_secureValueTypeUtilityBill: return Type::UtilityBill;
case mtpc_secureValueTypeBankStatement: return Type::BankStatement;
case mtpc_secureValueTypeRentalAgreement: return Type::RentalAgreement;
case mtpc_secureValueTypePhone: return Type::Phone;
case mtpc_secureValueTypeEmail: return Type::Email;
}
Unexpected("Type in secureValueType type.");
};
} // namespace
FormRequest::FormRequest(
@ -43,9 +60,11 @@ FormRequest::FormRequest(
}
EditFile::EditFile(
not_null<const Value*> value,
const File &fields,
std::unique_ptr<UploadScanData> &&uploadData)
: fields(std::move(fields))
: value(value)
, fields(std::move(fields))
, uploadData(std::move(uploadData))
, guard(std::make_shared<bool>(true)) {
}
@ -105,6 +124,10 @@ UserData *FormController::bot() const {
return _bot;
}
QString FormController::privacyPolicyUrl() const {
return _form.privacyPolicyUrl;
}
bytes::vector FormController::passwordHashForAuth(
bytes::const_span password) const {
return openssl::Sha256(bytes::concatenate(
@ -157,7 +180,7 @@ void FormController::validateSecureSecret(
_secretId = 0;
LOG(("API Error: Failed to decrypt secure secret. "
"Forgetting all files and data :("));
for (auto &value : _form.rows) {
for (auto &[type, value] : _form.values) {
if (!value.data.original.isEmpty()) {
resetValue(value);
}
@ -176,7 +199,7 @@ void FormController::validateSecureSecret(
void FormController::decryptValues() {
Expects(!_secret.empty());
for (auto &value : _form.rows) {
for (auto &[type, value] : _form.values) {
if (value.data.original.isEmpty()) {
continue;
}
@ -234,16 +257,17 @@ QString FormController::passwordHint() const {
return _password.hint;
}
void FormController::uploadScan(int valueIndex, QByteArray &&content) {
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
auto &value = _form.rows[valueIndex];
auto fileIndex = int(value.filesInEdit.size());
value.filesInEdit.emplace_back(
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(
nonconst,
File(),
nullptr);
const auto fileId = rand_value<uint64>();
auto &file = value.filesInEdit.back();
auto &file = nonconst->filesInEdit.back();
file.fields.size = content.size();
file.fields.id = fileId;
file.fields.dcId = MTP::maindc();
@ -254,19 +278,16 @@ void FormController::uploadScan(int valueIndex, QByteArray &&content) {
_scanUpdated.fire(&file);
encryptScan(valueIndex, fileIndex, std::move(content));
encryptScan(nonconst, fileIndex, std::move(content));
}
void FormController::encryptScan(
int valueIndex,
not_null<Value*> value,
int fileIndex,
QByteArray &&content) {
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size());
const auto &value = _form.rows[valueIndex];
const auto &file = value.filesInEdit[fileIndex];
const auto &file = value->filesInEdit[fileIndex];
const auto weak = std::weak_ptr<bool>(file.guard);
crl::async([
=,
@ -289,7 +310,7 @@ void FormController::encryptScan(
crl::on_main([=, encrypted = std::move(result)]() mutable {
if (weak.lock()) {
uploadEncryptedScan(
valueIndex,
value ,
fileIndex,
std::move(encrypted));
}
@ -297,23 +318,26 @@ void FormController::encryptScan(
});
}
void FormController::deleteScan(int valueIndex, int fileIndex) {
scanDeleteRestore(valueIndex, fileIndex, true);
void FormController::deleteScan(
not_null<const Value*> value,
int fileIndex) {
scanDeleteRestore(value, fileIndex, true);
}
void FormController::restoreScan(int valueIndex, int fileIndex) {
scanDeleteRestore(valueIndex, fileIndex, false);
void FormController::restoreScan(
not_null<const Value*> value,
int fileIndex) {
scanDeleteRestore(value, fileIndex, false);
}
void FormController::scanDeleteRestore(
int valueIndex,
not_null<const Value*> value,
int fileIndex,
bool deleted) {
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size());
auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
const auto nonconst = findValue(value);
auto &file = nonconst->filesInEdit[fileIndex];
file.deleted = deleted;
_scanUpdated.fire(&file);
}
@ -342,16 +366,14 @@ void FormController::subscribeToUploader() {
}
void FormController::uploadEncryptedScan(
int valueIndex,
not_null<Value*> value,
int fileIndex,
UploadScanData &&data) {
Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0
&& fileIndex < _form.rows[valueIndex].filesInEdit.size());
Expects(fileIndex >= 0 && fileIndex < value->filesInEdit.size());
subscribeToUploader();
auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
auto &file = value->filesInEdit[fileIndex];
file.uploadData = std::make_unique<UploadScanData>(std::move(data));
auto prepared = std::make_shared<FileLoadResult>(
@ -424,27 +446,32 @@ QString FormController::defaultPhoneNumber() const {
return QString();
}
rpl::producer<not_null<const EditFile*>> FormController::scanUpdated() const {
auto FormController::scanUpdated() const
-> rpl::producer<not_null<const EditFile*>> {
return _scanUpdated.events();
}
void FormController::enumerateRows(
base::lambda<void(const Value &value)> callback) {
ranges::for_each(_form.rows, callback);
const Form &FormController::form() const {
return _form;
}
not_null<Value*> FormController::startValueEdit(int index) {
Expects(index >= 0 && index < _form.rows.size());
not_null<Value*> FormController::findValue(not_null<const Value*> value) {
const auto i = _form.values.find(value->type);
Assert(i != end(_form.values));
const auto result = &i->second;
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);
Ensures(result == value);
return result;
}
void FormController::startValueEdit(not_null<const Value*> value) {
const auto nonconst = findValue(value);
loadFiles(nonconst->files);
nonconst->filesInEdit = ranges::view::all(
value->files
) | ranges::view::transform([=](const File &file) {
return EditFile(value, file, nullptr);
}) | ranges::to_vector;
return &value;
}
void FormController::loadFiles(std::vector<File> &files) {
@ -529,119 +556,169 @@ void FormController::fileLoadFail(FileKey key) {
}
}
void FormController::cancelValueEdit(int index) {
Expects(index >= 0 && index < _form.rows.size());
_form.rows[index].filesInEdit.clear();
void FormController::cancelValueEdit(not_null<const Value*> value) {
const auto nonconst = findValue(value);
nonconst->filesInEdit.clear();
}
bool FormController::isEncryptedValue(Value::Type type) const {
return (type == Value::Type::Identity || type == Value::Type::Address);
return (type != Value::Type::Phone && type != Value::Type::Email);
}
void FormController::saveValueEdit(int index) {
Expects(index >= 0 && index < _form.rows.size());
void FormController::saveValueEdit(
not_null<const Value*> value,
ValueMap &&data) {
const auto nonconst = findValue(value);
nonconst->data.parsed = std::move(data);
if (isEncryptedValue(_form.rows[index].type)) {
saveEncryptedValue(index);
if (isEncryptedValue(nonconst->type)) {
saveEncryptedValue(nonconst);
} else {
savePlainTextValue(index);
savePlainTextValue(nonconst);
}
}
void FormController::saveEncryptedValue(int index) {
Expects(index >= 0 && index < _form.rows.size());
Expects(isEncryptedValue(_form.rows[index].type));
void FormController::saveEncryptedValue(not_null<Value*> value) {
Expects(isEncryptedValue(value->type));
auto &value = _form.rows[index];
if (_secret.empty()) {
_secretCallbacks.push_back([=] {
saveEncryptedValue(index);
saveEncryptedValue(value);
});
return;
}
auto inputFiles = QVector<MTPInputSecureFile>();
inputFiles.reserve(value.filesInEdit.size());
for (const auto &file : value.filesInEdit) {
if (file.deleted) {
continue;
} else if (const auto uploadData = file.uploadData.get()) {
inputFiles.push_back(MTP_inputSecureFileUploaded(
const auto inputFile = [](const EditFile &file) {
if (const auto uploadData = file.uploadData.get()) {
return MTP_inputSecureFileUploaded(
MTP_long(file.fields.id),
MTP_int(uploadData->partsCount),
MTP_bytes(uploadData->md5checksum),
MTP_bytes(file.fields.hash),
MTP_bytes(file.fields.encryptedSecret)));
} else {
inputFiles.push_back(MTP_inputSecureFile(
MTP_long(file.fields.id),
MTP_long(file.fields.accessHash)));
MTP_bytes(file.fields.encryptedSecret));
}
return MTP_inputSecureFile(
MTP_long(file.fields.id),
MTP_long(file.fields.accessHash));
};
auto inputFiles = QVector<MTPInputSecureFile>();
inputFiles.reserve(value->filesInEdit.size());
for (const auto &file : value->filesInEdit) {
if (file.deleted) {
continue;
}
inputFiles.push_back(inputFile(file));
}
if (value.data.secret.empty()) {
value.data.secret = GenerateSecretBytes();
if (value->data.secret.empty()) {
value->data.secret = GenerateSecretBytes();
}
const auto encryptedData = EncryptData(
SerializeData(value.data.parsed.fields),
value.data.secret);
value.data.hash = encryptedData.hash;
value.data.encryptedSecret = EncryptValueSecret(
value.data.secret,
SerializeData(value->data.parsed.fields),
value->data.secret);
value->data.hash = encryptedData.hash;
value->data.encryptedSecret = EncryptValueSecret(
value->data.secret,
_secret,
value.data.hash);
value->data.hash);
const auto wrap = [&] {
switch (value.type) {
case Value::Type::Identity: return MTP_inputSecureValueIdentity;
case Value::Type::Address: return MTP_inputSecureValueAddress;
const auto selfie = value->selfieInEdit
? inputFile(*value->selfieInEdit)
: MTPInputSecureFile();
const auto type = [&] {
switch (value->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();
}
Unexpected("Value type in saveEncryptedValue().");
}();
sendSaveRequest(index, wrap(
MTP_secureData(
MTP_bytes(encryptedData.bytes),
MTP_bytes(value.data.hash),
MTP_bytes(value.data.encryptedSecret)),
MTP_vector<MTPInputSecureFile>(inputFiles)));
const auto flags = ((value->filesInEdit.empty()
&& value->data.parsed.fields.empty())
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_data)
| (value->filesInEdit.empty()
? MTPDinputSecureValue::Flag(0)
: MTPDinputSecureValue::Flag::f_files)
| (value->selfieInEdit
? MTPDinputSecureValue::Flag::f_selfie
: MTPDinputSecureValue::Flag(0));
if (!flags) {
request(MTPaccount_DeleteSecureValue(MTP_vector<MTPSecureValueType>(1, type))).send();
} else {
sendSaveRequest(value, MTP_inputSecureValue(
MTP_flags(flags),
type,
MTP_secureData(
MTP_bytes(encryptedData.bytes),
MTP_bytes(value->data.hash),
MTP_bytes(value->data.encryptedSecret)),
MTP_vector<MTPInputSecureFile>(inputFiles),
MTPSecurePlainData(),
selfie));
}
}
void FormController::savePlainTextValue(int index) {
Expects(index >= 0 && index < _form.rows.size());
Expects(!isEncryptedValue(_form.rows[index].type));
void FormController::savePlainTextValue(not_null<Value*> value) {
Expects(!isEncryptedValue(value->type));
auto &value = _form.rows[index];
const auto text = value.data.parsed.fields[QString("value")];
const auto wrap = [&] {
switch (value.type) {
case Value::Type::Phone: return MTP_inputSecureValuePhone;
case Value::Type::Email: return MTP_inputSecureValueEmail;
const auto text = value->data.parsed.fields[QString("value")];
const auto type = [&] {
switch (value->type) {
case Value::Type::Phone: return MTP_secureValueTypePhone();
case Value::Type::Email: return MTP_secureValueTypeEmail();
}
Unexpected("Value type in savePlainTextValue().");
}();
sendSaveRequest(index, wrap(MTP_string(text)));
const auto plain = [&] {
switch (value->type) {
case Value::Type::Phone: return MTP_securePlainPhone;
case Value::Type::Email: return MTP_securePlainEmail;
}
Unexpected("Value type in savePlainTextValue().");
}();
sendSaveRequest(value, MTP_inputSecureValue(
MTP_flags(MTPDinputSecureValue::Flag::f_plain_data),
type,
MTPSecureData(),
MTPVector<MTPInputSecureFile>(),
plain(MTP_string(text)),
MTPInputSecureFile()));
}
void FormController::sendSaveRequest(
int index,
const MTPInputSecureValue &value) {
Expects(index >= 0 && index < _form.rows.size());
not_null<Value*> value,
const MTPInputSecureValue &data) {
request(MTPaccount_SaveSecureValue(
value,
data,
MTP_long(_secretId)
)).done([=](const MTPSecureValueSaved &result) {
Expects(result.type() == mtpc_secureValueSaved);
)).done([=](const MTPSecureValue &result) {
Expects(result.type() == mtpc_secureValue);
const auto &data = result.c_secureValueSaved();
_form.rows[index].files = parseFiles(
const auto &data = result.c_secureValue();
value->files = parseFiles(
data.vfiles.v,
base::take(_form.rows[index].filesInEdit));
base::take(value->filesInEdit));
Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther);
}).fail([=](const RPCError &error) {
Ui::show(Box<InformBox>("Error saving value."));
Ui::show(Box<InformBox>("Error saving value"));
}).send();
}
@ -710,24 +787,6 @@ void FormController::requestForm() {
}).send();
}
template <typename DataType>
auto FormController::parseEncryptedValue(
Value::Type type,
const DataType &data) const -> Value {
Expects(data.vdata.type() == mtpc_secureData);
auto result = Value(type);
if (data.has_verified()) {
result.verification = parseVerified(data.vverified);
}
const auto &fields = data.vdata.c_secureData();
result.data.original = fields.vdata.v;
result.data.hash = bytes::make_vector(fields.vdata_hash.v);
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
result.files = parseFiles(data.vfiles.v);
return result;
}
auto FormController::parseFiles(
const QVector<MTPSecureFile> &data,
const std::vector<EditFile> &editData) const
@ -788,60 +847,41 @@ void FormController::fillDownloadedFile(
i->uploadData->bytes.size())));
}
template <typename DataType>
auto FormController::parsePlainTextValue(
Value::Type type,
const QByteArray &text,
const DataType &data) const -> Value {
auto FormController::parseValue(
const MTPSecureValue &value) const -> Value {
Expects(value.type() == mtpc_secureValue);
const auto &data = value.c_secureValue();
const auto type = ConvertType(data.vtype);
auto result = Value(type);
result.data.parsed.fields[QString("value")] = QString::fromUtf8(text);
if (data.has_verified()) {
result.verification = parseVerified(data.vverified);
if (data.has_data()) {
Assert(data.vdata.type() == mtpc_secureData);
const auto &fields = data.vdata.c_secureData();
result.data.original = fields.vdata.v;
result.data.hash = bytes::make_vector(fields.vdata_hash.v);
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
}
if (data.has_files()) {
result.files = parseFiles(data.vfiles.v);
}
if (data.has_plain_data()) {
switch (data.vplain_data.type()) {
case mtpc_securePlainPhone: {
const auto &fields = data.vplain_data.c_securePlainPhone();
result.data.parsed.fields["value"] = qs(fields.vphone);
} break;
case mtpc_securePlainEmail: {
const auto &fields = data.vplain_data.c_securePlainEmail();
result.data.parsed.fields["value"] = qs(fields.vemail);
} break;
}
}
// #TODO passport selfie
return result;
}
auto FormController::parseValue(
const MTPSecureValue &value) const -> Value {
switch (value.type()) {
case mtpc_secureValueIdentity: {
return parseEncryptedValue(
Value::Type::Identity,
value.c_secureValueIdentity());
} break;
case mtpc_secureValueAddress: {
return parseEncryptedValue(
Value::Type::Address,
value.c_secureValueAddress());
} break;
case mtpc_secureValuePhone: {
const auto &data = value.c_secureValuePhone();
return parsePlainTextValue(
Value::Type::Phone,
data.vphone.v,
data);
} break;
case mtpc_secureValueEmail: {
const auto &data = value.c_secureValueEmail();
return parsePlainTextValue(
Value::Type::Phone,
data.vemail.v,
data);
} break;
}
Unexpected("secureValue type.");
}
auto FormController::parseVerified(const MTPSecureValueVerified &data) const
-> Verification {
Expects(data.type() == mtpc_secureValueVerified);
const auto &fields = data.c_secureValueVerified();
return Verification{ fields.vdate.v };
}
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
for (auto &value : _form.rows) {
for (auto &[type, value] : _form.values) {
for (auto &file : value.filesInEdit) {
if (file.uploadData && file.uploadData->fullId == fullId) {
return &file;
@ -852,7 +892,7 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
}
auto FormController::findEditFile(const FileKey &key) -> EditFile* {
for (auto &value : _form.rows) {
for (auto &[type, value] : _form.values) {
for (auto &file : value.filesInEdit) {
if (file.fields.dcId == key.dcId && file.fields.id == key.id) {
return &file;
@ -864,7 +904,7 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* {
auto FormController::findFile(const FileKey &key)
-> std::pair<Value*, File*> {
for (auto &value : _form.rows) {
for (auto &[type, value] : _form.values) {
for (auto &file : value.files) {
if (file.dcId == key.dcId && file.id == key.id) {
return { &value, &file };
@ -886,49 +926,33 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
const auto &data = result.c_account_authorizationForm();
auto values = std::vector<Value>();
for (const auto &value : data.vvalues.v) {
values.push_back(parseValue(value));
}
const auto findValue = [&](Value::Type type) -> Value* {
for (auto &value : values) {
if (value.type == type) {
return &value;
}
}
return nullptr;
};
App::feedUsers(data.vusers);
for (const auto &required : data.vrequired_types.v) {
using Type = Value::Type;
const auto type = [&] {
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;
}
Unexpected("Type in secureValueType type.");
}();
if (auto value = findValue(type)) {
_form.rows.push_back(std::move(*value));
} else {
_form.rows.push_back(Value(type));
for (const auto &value : data.vvalues.v) {
auto parsed = parseValue(value);
const auto type = parsed.type;
const auto alreadyIt = _form.values.find(type);
if (alreadyIt != _form.values.end()) {
LOG(("API Error: Two values for type %1 in authorization form"
"%1").arg(int(type)));
continue;
}
_form.values.emplace(type, std::move(parsed));
}
_form.identitySelfieRequired = data.is_selfie_required();
if (data.has_privacy_policy_url()) {
_form.privacyPolicyUrl = qs(data.vprivacy_policy_url);
}
for (const auto &required : data.vrequired_types.v) {
const auto type = ConvertType(required);
_form.request.push_back(type);
_form.values.emplace(type, Value(type));
}
_bot = App::userLoaded(_request.botId);
}
void FormController::formFail(const RPCError &error) {
// #TODO passport testing
_form.rows.push_back(Value(Value::Type::Identity));
_form.rows.back().data.parsed.fields["first_name"] = "test1";
_form.rows.back().data.parsed.fields["last_name"] = "test2";
_view->editValue(0);
// Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
Ui::show(Box<InformBox>(lang(lng_passport_form_error)));
}
void FormController::requestPassword() {

View File

@ -67,6 +67,8 @@ private:
};
struct Value;
struct File {
uint64 id = 0;
uint64 accessHash = 0;
@ -83,19 +85,17 @@ struct File {
struct EditFile {
EditFile(
not_null<const Value*> value,
const File &fields,
std::unique_ptr<UploadScanData> &&uploadData);
not_null<const Value*> value;
File fields;
UploadScanDataPointer uploadData;
std::shared_ptr<bool> guard;
bool deleted = false;
};
struct Verification {
TimeId date;
};
struct ValueMap {
std::map<QString, QString> fields;
};
@ -110,8 +110,14 @@ struct ValueData {
struct Value {
enum class Type {
Identity,
PersonalDetails,
Passport,
DriverLicense,
IdentityCard,
Address,
UtilityBill,
BankStatement,
RentalAgreement,
Phone,
Email,
};
@ -124,11 +130,15 @@ struct Value {
ValueData data;
std::vector<File> files;
std::vector<EditFile> filesInEdit;
base::optional<Verification> verification;
base::optional<File> selfie;
base::optional<EditFile> selfieInEdit;
};
struct Form {
std::vector<Value> rows;
std::map<Value::Type, Value> values;
std::vector<Value::Type> request;
bool identitySelfieRequired = false;
QString privacyPolicyUrl;
};
struct PasswordSettings {
@ -174,14 +184,15 @@ public:
void show();
UserData *bot() const;
QString privacyPolicyUrl() const;
void submitPassword(const QString &password);
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
void uploadScan(int valueIndex, QByteArray &&content);
void deleteScan(int valueIndex, int fileIndex);
void restoreScan(int valueIndex, int fileIndex);
void uploadScan(not_null<const Value*> value, QByteArray &&content);
void deleteScan(not_null<const Value*> value, int fileIndex);
void restoreScan(not_null<const Value*> value, int fileIndex);
rpl::producer<> secretReadyEvents() const;
@ -190,10 +201,10 @@ public:
rpl::producer<not_null<const EditFile*>> scanUpdated() const;
void enumerateRows(base::lambda<void(const Value &value)> callback);
not_null<Value*> startValueEdit(int index);
void cancelValueEdit(int index);
void saveValueEdit(int index);
const Form &form() const;
void startValueEdit(not_null<const Value*> value);
void cancelValueEdit(not_null<const Value*> value);
void saveValueEdit(not_null<const Value*> value, ValueMap &&data);
void cancel();
@ -205,6 +216,7 @@ private:
EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key);
std::pair<Value*, File*> findFile(const FileKey &key);
not_null<Value*> findValue(not_null<const Value*> value);
void requestForm();
void requestPassword();
@ -214,16 +226,6 @@ private:
void parseForm(const MTPaccount_AuthorizationForm &result);
void showForm();
Value parseValue(const MTPSecureValue &value) const;
template <typename DataType>
Value parseEncryptedValue(
Value::Type type,
const DataType &data) const;
template <typename DataType>
Value parsePlainTextValue(
Value::Type type,
const QByteArray &text,
const DataType &data) const;
Verification parseVerified(const MTPSecureValueVerified &data) const;
std::vector<File> parseFiles(
const QVector<MTPSecureFile> &data,
const std::vector<EditFile> &editData = {}) const;
@ -253,22 +255,24 @@ private:
void subscribeToUploader();
void encryptScan(
int valueIndex,
not_null<Value*> value,
int fileIndex,
QByteArray &&content);
void uploadEncryptedScan(
int valueIndex,
not_null<Value*> value,
int fileIndex,
UploadScanData &&data);
void scanUploadDone(const Storage::UploadSecureDone &data);
void scanUploadProgress(const Storage::UploadSecureProgress &data);
void scanUploadFail(const FullMsgId &fullId);
void scanDeleteRestore(int valueIndex, int fileIndex, bool deleted);
void scanDeleteRestore(not_null<const Value*> value, int fileIndex, bool deleted);
bool isEncryptedValue(Value::Type type) const;
void saveEncryptedValue(int index);
void savePlainTextValue(int index);
void sendSaveRequest(int index, const MTPInputSecureValue &value);
void saveEncryptedValue(not_null<Value*> value);
void savePlainTextValue(not_null<Value*> value);
void sendSaveRequest(
not_null<Value*> value,
const MTPInputSecureValue &data);
not_null<Window::Controller*> _controller;
FormRequest _request;

View File

@ -0,0 +1,96 @@
/*
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_view_controller.h"
#include "passport/passport_form_controller.h"
namespace Passport {
namespace {
std::map<Value::Type, Scope::Type> ScopeTypesMap() {
return {
{ Value::Type::PersonalDetails, Scope::Type::Identity },
{ Value::Type::Passport, Scope::Type::Identity },
{ Value::Type::DriverLicense, Scope::Type::Identity },
{ Value::Type::IdentityCard, Scope::Type::Identity },
{ Value::Type::Address, Scope::Type::Address },
{ Value::Type::UtilityBill, Scope::Type::Address },
{ Value::Type::BankStatement, Scope::Type::Address },
{ Value::Type::RentalAgreement, Scope::Type::Address },
{ Value::Type::Phone, Scope::Type::Phone },
{ Value::Type::Email, Scope::Type::Email },
};
}
Scope::Type ScopeTypeForValueType(Value::Type type) {
static const auto map = ScopeTypesMap();
const auto i = map.find(type);
Assert(i != map.end());
return i->second;
}
std::map<Scope::Type, Value::Type> ScopeFieldsMap() {
return {
{ Scope::Type::Identity, Value::Type::PersonalDetails },
{ Scope::Type::Address, Value::Type::Address },
{ Scope::Type::Phone, Value::Type::Phone },
{ Scope::Type::Email, Value::Type::Email },
};
}
Value::Type FieldsTypeForScopeType(Scope::Type type) {
static const auto map = ScopeFieldsMap();
const auto i = map.find(type);
Assert(i != map.end());
return i->second;
}
} // namespace
Scope::Scope(Type type, not_null<const Value*> fields)
: type(type)
, fields(fields) {
}
std::vector<Scope> ComputeScopes(not_null<FormController*> controller) {
auto scopes = std::map<Scope::Type, Scope>();
const auto &form = controller->form();
const auto findValue = [&](const Value::Type type) {
const auto i = form.values.find(type);
Assert(i != form.values.end());
return &i->second;
};
for (const auto type : form.request) {
const auto scopeType = ScopeTypeForValueType(type);
const auto fieldsType = FieldsTypeForScopeType(scopeType);
const auto [i, ok] = scopes.emplace(
scopeType,
Scope(scopeType, findValue(fieldsType)));
i->second.selfieRequired = (scopeType == Scope::Type::Identity)
&& form.identitySelfieRequired;
const auto alreadyIt = ranges::find(
i->second.files,
type,
[](not_null<const Value*> value) { return value->type; });
if (alreadyIt != end(i->second.files)) {
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));
}
}
auto result = std::vector<Scope>();
result.reserve(scopes.size());
for (auto &[type, scope] : scopes) {
result.push_back(std::move(scope));
}
return result;
}
} // namespace Passport

View File

@ -7,16 +7,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "passport/passport_form_controller.h"
namespace Passport {
struct Value;
struct Scope {
enum class Type {
Identity,
Address,
Phone,
Email,
};
Scope(Type type, not_null<const Value*> fields);
Type type;
not_null<const Value*> fields;
std::vector<not_null<const Value*>> files;
bool selfieRequired = false;
};
std::vector<Scope> ComputeScopes(not_null<FormController*> controller);
class ViewController {
public:
virtual void showAskPassword() = 0;
virtual void showNoPassword() = 0;
virtual void showPasswordUnconfirmed() = 0;
virtual void editValue(int index) = 0;
virtual void editScope(int index) = 0;
virtual ~ViewController() {
}

View File

@ -51,52 +51,61 @@ BoxContent *BoxPointer::operator->() const {
}
PanelController::PanelController(not_null<FormController*> form)
: _form(form) {
: _form(form)
, _scopes(ComputeScopes(_form)) {
_form->secretReadyEvents(
) | rpl::start_with_next([=] {
if (_panel) {
_panel->showForm();
}
}, lifetime());
_scopes = ComputeScopes(_form);
}
not_null<UserData*> PanelController::bot() const {
return _form->bot();
}
QString PanelController::privacyPolicyUrl() const {
return _form->privacyPolicyUrl();
}
void PanelController::fillRows(
base::lambda<void(
QString title,
QString description,
bool ready)> callback) {
_form->enumerateRows([&](const Value &value) {
switch (value.type) {
case Value::Type::Identity:
if (_scopes.empty()) {
_scopes = ComputeScopes(_form);
}
for (const auto &scope : _scopes) {
switch (scope.type) {
case Scope::Type::Identity:
callback(
lang(lng_passport_identity_title),
lang(lng_passport_identity_description),
false);
break;
case Value::Type::Address:
case Scope::Type::Address:
callback(
lang(lng_passport_address_title),
lang(lng_passport_address_description),
false);
break;
case Value::Type::Phone:
case Scope::Type::Phone:
callback(
lang(lng_passport_phone_title),
App::self()->phone(),
true);
lang(lng_passport_phone_description),
false);
break;
case Value::Type::Email:
case Scope::Type::Email:
callback(
lang(lng_passport_email_title),
lang(lng_passport_email_description),
false);
break;
}
});
}
}
void PanelController::submitPassword(const QString &password) {
@ -119,27 +128,40 @@ QString PanelController::defaultPhoneNumber() const {
return _form->defaultPhoneNumber();
}
void PanelController::uploadScan(int valueIndex, QByteArray &&content) {
Expects(_panel != nullptr);
void PanelController::uploadScan(QByteArray &&content) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0);
_form->uploadScan(valueIndex, std::move(content));
_form->uploadScan(
_editScope->files[_editScopeFilesIndex],
std::move(content));
}
void PanelController::deleteScan(int valueIndex, int fileIndex) {
Expects(_panel != nullptr);
void PanelController::deleteScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0);
_form->deleteScan(valueIndex, fileIndex);
_form->deleteScan(
_editScope->files[_editScopeFilesIndex],
fileIndex);
}
void PanelController::restoreScan(int valueIndex, int fileIndex) {
Expects(_panel != nullptr);
void PanelController::restoreScan(int fileIndex) {
Expects(_editScope != nullptr);
Expects(_editScopeFilesIndex >= 0);
_form->restoreScan(valueIndex, fileIndex);
_form->restoreScan(
_editScope->files[_editScopeFilesIndex],
fileIndex);
}
rpl::producer<ScanInfo> PanelController::scanUpdated() const {
return _form->scanUpdated(
) | rpl::map([=](not_null<const EditFile*> file) {
) | rpl::filter([=](not_null<const EditFile*> file) {
return (_editScope != nullptr)
&& (_editScopeFilesIndex >= 0)
&& (file->value == _editScope->files[_editScopeFilesIndex]);
}) | rpl::map([=](not_null<const EditFile*> file) {
return collectScanInfo(*file);
});
}
@ -202,22 +224,29 @@ void PanelController::ensurePanelCreated() {
}
}
void PanelController::editValue(int index) {
ensurePanelCreated(); // #TODO passport testing
void PanelController::editScope(int index) {
Expects(_panel != nullptr);
Expects(index >= 0 && index < _scopes.size());
_editValue = _form->startValueEdit(index);
Assert(_editValue != nullptr);
_editScope = &_scopes[index];
// #TODO select type for files index
_editScopeFilesIndex = _scopes[index].files.empty() ? -1 : 0;
_form->startValueEdit(_editScope->fields);
if (_editScopeFilesIndex >= 0) {
_form->startValueEdit(_editScope->files[_editScopeFilesIndex]);
}
auto content = [&]() -> object_ptr<Ui::RpWidget> {
switch (_editValue->type) {
case Value::Type::Identity:
switch (_editScope->type) {
case Scope::Type::Identity:
return object_ptr<PanelEditIdentity>(
_panel.get(),
this,
index,
_editValue->data.parsed,
valueFiles(*_editValue));
_editScope->fields->data.parsed,
_editScope->files[_editScopeFilesIndex]->data.parsed,
valueFiles(*_editScope->files[_editScopeFilesIndex]));
}
return { nullptr };
}();
@ -244,21 +273,29 @@ std::vector<ScanInfo> PanelController::valueFiles(const Value &value) const {
}
void PanelController::cancelValueEdit(int index) {
if (base::take(_editValue)) {
_form->cancelValueEdit(index);
if (const auto scope = base::take(_editScope)) {
_form->startValueEdit(scope->fields);
const auto index = std::exchange(_editScopeFilesIndex, -1);
if (index >= 0) {
_form->startValueEdit(scope->files[index]);
}
}
}
void PanelController::saveValue(int index, ValueMap &&data) {
void PanelController::saveScope(ValueMap &&data, ValueMap &&filesData) {
Expects(_panel != nullptr);
Expects(_editValue != nullptr);
Expects(_editScope != nullptr);
_editValue->data.parsed = std::move(data);
_editValue = nullptr;
const auto scope = base::take(_editScope);
_form->saveValueEdit(scope->fields, std::move(data));
const auto index = std::exchange(_editScopeFilesIndex, -1);
if (index >= 0) {
_form->saveValueEdit(scope->files[index], std::move(filesData));
} else {
Assert(filesData.fields.empty());
}
_panel->showForm();
_form->saveValueEdit(index);
}
void PanelController::cancelAuth() {

View File

@ -45,14 +45,15 @@ public:
PanelController(not_null<FormController*> form);
not_null<UserData*> bot() const;
QString privacyPolicyUrl() const;
void submitPassword(const QString &password);
rpl::producer<QString> passwordError() const;
QString passwordHint() const;
void uploadScan(int valueIndex, QByteArray &&content);
void deleteScan(int valueIndex, int fileIndex);
void restoreScan(int valueIndex, int fileIndex);
void uploadScan(QByteArray &&content);
void deleteScan(int fileIndex);
void restoreScan(int fileIndex);
rpl::producer<ScanInfo> scanUpdated() const;
QString defaultEmail() const;
@ -68,8 +69,8 @@ public:
QString description,
bool ready)> callback);
void editValue(int index) override;
void saveValue(int index, ValueMap &&data);
void editScope(int index) override;
void saveScope(ValueMap &&data, ValueMap &&filesData);
void cancelAuth();
@ -84,9 +85,11 @@ private:
ScanInfo collectScanInfo(const EditFile &file) const;
not_null<FormController*> _form;
std::vector<Scope> _scopes;
std::unique_ptr<Panel> _panel;
Value *_editValue = nullptr;
Scope *_editScope = nullptr;
int _editScopeFilesIndex = -1;
rpl::lifetime _lifetime;

View File

@ -17,7 +17,7 @@ PanelDetailsRow::PanelDetailsRow(
const QString &label,
const QString &value)
: _label(label)
, _field(this, st::passportDetailsField) {
, _field(this, st::passportDetailsField, nullptr, value) {
}
QPointer<Ui::InputField> PanelDetailsRow::field() const {

View File

@ -190,11 +190,10 @@ void ScanButton::paintEvent(QPaintEvent *e) {
PanelEditIdentity::PanelEditIdentity(
QWidget*,
not_null<PanelController*> controller,
int valueIndex,
const ValueMap &data,
const ValueMap &scanData,
std::vector<ScanInfo> &&files)
: _controller(controller)
, _valueIndex(valueIndex)
, _files(std::move(files))
, _scroll(this, st::passportPanelScroll)
, _topShadow(this)
@ -203,11 +202,13 @@ PanelEditIdentity::PanelEditIdentity(
this,
langFactory(lng_passport_save_value),
st::passportPanelSaveValue) {
setupControls(data);
setupControls(data, scanData);
}
void PanelEditIdentity::setupControls(const ValueMap &data) {
const auto inner = setupContent(data);
void PanelEditIdentity::setupControls(
const ValueMap &data,
const ValueMap &scanData) {
const auto inner = setupContent(data, scanData);
using namespace rpl::mappers;
@ -225,7 +226,8 @@ void PanelEditIdentity::setupControls(const ValueMap &data) {
}
not_null<Ui::RpWidget*> PanelEditIdentity::setupContent(
const ValueMap &data) {
const ValueMap &data,
const ValueMap &scanData) {
const auto inner = _scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(this));
_scroll->widthValue(
@ -261,7 +263,9 @@ not_null<Ui::RpWidget*> PanelEditIdentity::setupContent(
_scansUpload = inner->add(
object_ptr<Info::Profile::Button>(
inner,
uploadButtonText(),
_scansUploadTexts.events_starting_with(
uploadButtonText()
) | rpl::flatten_latest(),
st::passportUploadButton),
st::passportUploadButtonPadding);
_scansUpload->addClickHandler([=] {
@ -315,6 +319,7 @@ void PanelEditIdentity::updateScan(ScanInfo &&info) {
_scans.back()->show(anim::type::normal);
_scansDivider->hide(anim::type::normal);
_scansHeader->show(anim::type::normal);
_scansUploadTexts.fire(uploadButtonText());
}
}
@ -335,12 +340,12 @@ void PanelEditIdentity::pushScan(const ScanInfo &info) {
scan->deleteClicks(
) | rpl::start_with_next([=] {
_controller->deleteScan(_valueIndex, index);
_controller->deleteScan(index);
}, scan->lifetime());
scan->restoreClicks(
) | rpl::start_with_next([=] {
_controller->restoreScan(_valueIndex, index);
_controller->restoreScan(index);
}, scan->lifetime());
}
@ -394,14 +399,15 @@ void PanelEditIdentity::encryptScan(const QString &path) {
}
void PanelEditIdentity::encryptScanContent(QByteArray &&content) {
_controller->uploadScan(_valueIndex, std::move(content));
_controller->uploadScan(std::move(content));
}
void PanelEditIdentity::save() {
auto data = ValueMap();
data.fields["first_name"] = _firstName->getLastText();
data.fields["last_name"] = _lastName->getLastText();
_controller->saveValue(_valueIndex, std::move(data));
auto scanData = ValueMap();
_controller->saveScope(std::move(data), std::move(scanData));
}
rpl::producer<QString> PanelEditIdentity::uploadButtonText() const {

View File

@ -41,8 +41,8 @@ public:
PanelEditIdentity(
QWidget *parent,
not_null<PanelController*> controller,
int valueIndex,
const ValueMap &data,
const ValueMap &scanData,
std::vector<ScanInfo> &&files);
protected:
@ -50,8 +50,10 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void setupControls(const ValueMap &data);
not_null<Ui::RpWidget*> setupContent(const ValueMap &data);
void setupControls(const ValueMap &data, const ValueMap &scanData);
not_null<Ui::RpWidget*> setupContent(
const ValueMap &data,
const ValueMap &scanData);
void updateControlsGeometry();
void chooseScan();
@ -64,8 +66,6 @@ private:
void save();
not_null<PanelController*> _controller;
int _valueIndex = -1;
std::vector<ScanInfo> _files;
object_ptr<Ui::ScrollArea> _scroll;
@ -77,6 +77,7 @@ private:
QPointer<Ui::VerticalLayout> _scansWrap;
std::vector<base::unique_qptr<Ui::SlideWrap<ScanButton>>> _scans;
QPointer<Info::Profile::Button> _scansUpload;
rpl::event_stream<rpl::producer<QString>> _scansUploadTexts;
QPointer<Ui::InputField> _firstName;
QPointer<Ui::InputField> _lastName;

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_controller.h"
#include "lang/lang_keys.h"
#include "boxes/abstract_box.h"
#include "core/click_handler_types.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
@ -221,27 +222,30 @@ not_null<Ui::RpWidget*> PanelForm::setupContent() {
title,
description)));
_rows.back()->addClickHandler([=] {
_controller->editValue(index);
_controller->editScope(index);
});
_rows.back()->setReady(ready);
++index;
});
const auto policyUrl = _controller->privacyPolicyUrl();
const auto policy = inner->add(
object_ptr<Ui::FlatLabel>(
inner,
lng_passport_accept_allow(
lt_policy,
textcmdLink(
1,
lng_passport_policy(lt_bot, App::peerName(bot))),
lt_bot,
'@' + bot->username),
(policyUrl.isEmpty()
? lng_passport_allow(lt_bot, '@' + bot->username)
: lng_passport_accept_allow(
lt_policy,
textcmdLink(
1,
lng_passport_policy(lt_bot, App::peerName(bot))),
lt_bot,
'@' + bot->username)),
Ui::FlatLabel::InitType::Rich,
st::passportFormPolicy),
st::passportFormPolicyPadding);
policy->setLink(1, std::make_shared<LambdaClickHandler>([=] {
_controller->cancelAuth();
// #TODO passport policy
}));
if (!policyUrl.isEmpty()) {
policy->setLink(1, std::make_shared<UrlClickHandler>(policyUrl));
}
return inner;
}

View File

@ -458,6 +458,7 @@
<(src_loc)/passport/passport_encryption.h
<(src_loc)/passport/passport_form_controller.cpp
<(src_loc)/passport/passport_form_controller.h
<(src_loc)/passport/passport_form_view_controller.cpp
<(src_loc)/passport/passport_form_view_controller.h
<(src_loc)/passport/passport_panel.cpp
<(src_loc)/passport/passport_panel.h