Bot api 4.1 (#1198)

* Fix passport decryption failing at random times

Sometimes a decrypted secret was being treated as b64 and therefore got decoded even further. Fix by decoding b64 right before call to decrypt so we have better control of when not to do it

* Bot api 4.1

Telegram passport 1.1
Added support for middle names.
Added support for translations for documents
Add errors for translations for documents
Added support for requesting names in the language of the user's country of residence
Replaced the payload parameter with the new parameter nonce

NOTE: Scope stuff is NOT implemented, as we wanna STRONGLY encourage users to use the telegram provided SDKs anyway (and not generate telegram auth links in their bot, but rather on a server)

* Minor fixes

* Add hash to EncryptedPassportElement

For use with PassportElementErrorUnspecified apparently
This commit is contained in:
Jasmin Bom 2018-09-01 16:58:08 +02:00 committed by GitHub
parent 937be3d2e8
commit 09bdb88822
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 550 additions and 101 deletions

View file

@ -24,9 +24,9 @@ def msg(bot, update):
# If we received any passport data # If we received any passport data
passport_data = update.message.passport_data passport_data = update.message.passport_data
if passport_data: if passport_data:
# If our payload doesn't match what we think, this Update did not originate from us # If our nonce doesn't match what we think, this Update did not originate from us
# Ideally you would randomize the payload on the server # Ideally you would randomize the nonce on the server
if passport_data.decrypted_credentials.payload != 'thisisatest': if passport_data.decrypted_credentials.nonce != 'thisisatest':
return return
# Print the decrypted credential data # Print the decrypted credential data
@ -39,7 +39,7 @@ def msg(bot, update):
elif data.type == 'email': elif data.type == 'email':
print('Email: ', data.email) print('Email: ', data.email)
if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card', if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card',
'identity_passport', 'address'): 'internal_passport', 'address'):
print(data.type, data.data) print(data.type, data.data)
if data.type in ('utility_bill', 'bank_statement', 'rental_agreement', if data.type in ('utility_bill', 'bank_statement', 'rental_agreement',
'passport_registration', 'temporary_registration'): 'passport_registration', 'temporary_registration'):
@ -65,6 +65,15 @@ def msg(bot, update):
file = data.selfie.get_file() file = data.selfie.get_file()
print(data.type, file) print(data.type, file)
file.download() file.download()
if data.type in ('passport', 'driver_license', 'identity_card',
'internal_passport', 'utility_bill', 'bank_statement',
'rental_agreement', 'passport_registration',
'temporary_registration'):
print(data.type, len(data.translation), 'translation')
for file in data.translation:
actual_file = file.get_file()
print(actual_file)
actual_file.download()
def error(bot, update, error): def error(bot, update, error):

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python ''#!/usr/bin/env python
# #
# A library that provides a Python interface to the Telegram Bot API # A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018 # Copyright (C) 2015-2018
@ -109,7 +109,10 @@ from .passport.passportelementerrors import (PassportElementError,
PassportElementErrorFiles, PassportElementErrorFiles,
PassportElementErrorFrontSide, PassportElementErrorFrontSide,
PassportElementErrorReverseSide, PassportElementErrorReverseSide,
PassportElementErrorSelfie) PassportElementErrorSelfie,
PassportElementErrorTranslationFile,
PassportElementErrorTranslationFiles,
PassportElementErrorUnspecified)
from .passport.credentials import (Credentials, from .passport.credentials import (Credentials,
DataCredentials, DataCredentials,
SecureData, SecureData,
@ -148,5 +151,6 @@ __all__ = [
'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData', 'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData',
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation', 'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError', 'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
'PassportElementErrorSelfie' 'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified'
] ]

View file

@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram File.""" """This module contains an object that represents a Telegram File."""
from base64 import b64decode
from os.path import basename from os.path import basename
from future.backports.urllib import parse as urllib_parse from future.backports.urllib import parse as urllib_parse
@ -107,7 +108,9 @@ class File(TelegramObject):
if out: if out:
buf = self.bot.request.retrieve(url) buf = self.bot.request.retrieve(url)
if self._credentials: if self._credentials:
buf = decrypt(self._credentials.secret, self._credentials.hash, buf, file=True) buf = decrypt(b64decode(self._credentials.secret),
b64decode(self._credentials.hash),
buf)
out.write(buf) out.write(buf)
return out return out
else: else:
@ -118,7 +121,9 @@ class File(TelegramObject):
buf = self.bot.request.retrieve(url, timeout=timeout) buf = self.bot.request.retrieve(url, timeout=timeout)
if self._credentials: if self._credentials:
buf = decrypt(self._credentials.secret, self._credentials.hash, buf, file=True) buf = decrypt(b64decode(self._credentials.secret),
b64decode(self._credentials.hash),
buf)
with open(filename, 'wb') as fobj: with open(filename, 'wb') as fobj:
fobj.write(buf) fobj.write(buf)
return filename return filename

View file

@ -16,7 +16,6 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
import binascii
try: try:
import ujson as json import ujson as json
except ImportError: except ImportError:
@ -44,7 +43,7 @@ class TelegramDecryptionError(TelegramError):
"{}".format(message)) "{}".format(message))
def decrypt(secret, hash, data, file=False): def decrypt(secret, hash, data):
""" """
Decrypt per telegram docs at https://core.telegram.org/passport. Decrypt per telegram docs at https://core.telegram.org/passport.
@ -65,20 +64,6 @@ def decrypt(secret, hash, data, file=False):
:obj:`bytes`: The decrypted data as bytes. :obj:`bytes`: The decrypted data as bytes.
""" """
# First make sure that if secret, hash, or data was base64 encoded, to decode it into bytes
try:
secret = b64decode(secret)
except (binascii.Error, TypeError):
pass
try:
hash = b64decode(hash)
except (binascii.Error, TypeError):
pass
if not file:
try:
data = b64decode(data)
except (binascii.Error, TypeError):
pass
# Make a SHA512 hash of secret + update # Make a SHA512 hash of secret + update
digest = Hash(SHA512(), backend=default_backend()) digest = Hash(SHA512(), backend=default_backend())
digest.update(secret + hash) digest.update(secret + hash)
@ -113,14 +98,14 @@ class EncryptedCredentials(TelegramObject):
Attributes: Attributes:
data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's
payload, data hashes and secrets used for EncryptedPassportElement decryption and nonce, data hashes and secrets used for EncryptedPassportElement decryption and
authentication or base64 encrypted data. authentication or base64 encrypted data.
hash (:obj:`str`): Base64-encoded data hash for data authentication. hash (:obj:`str`): Base64-encoded data hash for data authentication.
secret (:obj:`str`): Decrypted or encrypted secret used for decryption. secret (:obj:`str`): Decrypted or encrypted secret used for decryption.
Args: Args:
data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's
payload, data hashes and secrets used for EncryptedPassportElement decryption and nonce, data hashes and secrets used for EncryptedPassportElement decryption and
authentication or base64 encrypted data. authentication or base64 encrypted data.
hash (:obj:`str`): Base64-encoded data hash for data authentication. hash (:obj:`str`): Base64-encoded data hash for data authentication.
secret (:obj:`str`): Decrypted or encrypted secret used for decryption. secret (:obj:`str`): Decrypted or encrypted secret used for decryption.
@ -184,8 +169,8 @@ class EncryptedCredentials(TelegramObject):
def decrypted_data(self): def decrypted_data(self):
""" """
:class:`telegram.Credentials`: Lazily decrypt and return credentials data. This object :class:`telegram.Credentials`: Lazily decrypt and return credentials data. This object
also contains the user specified payload as also contains the user specified nonce as
`decrypted_data.payload`. `decrypted_data.nonce`.
Raises: Raises:
telegram.TelegramDecryptionError: Decryption failed. Usually due to bad telegram.TelegramDecryptionError: Decryption failed. Usually due to bad
@ -193,8 +178,8 @@ class EncryptedCredentials(TelegramObject):
""" """
if self._decrypted_data is None: if self._decrypted_data is None:
self._decrypted_data = Credentials.de_json(decrypt_json(self.decrypted_secret, self._decrypted_data = Credentials.de_json(decrypt_json(self.decrypted_secret,
self.hash, b64decode(self.hash),
self.data), b64decode(self.data)),
self.bot) self.bot)
return self._decrypted_data return self._decrypted_data
@ -203,13 +188,13 @@ class Credentials(TelegramObject):
""" """
Attributes: Attributes:
secure_data (:class:`telegram.SecureData`): Credentials for encrypted data secure_data (:class:`telegram.SecureData`): Credentials for encrypted data
payload (:obj:`str`): Bot-specified payload nonce (:obj:`str`): Bot-specified nonce
""" """
def __init__(self, secure_data, payload, bot=None, **kwargs): def __init__(self, secure_data, nonce, bot=None, **kwargs):
# Required # Required
self.secure_data = secure_data self.secure_data = secure_data
self.payload = payload self.nonce = nonce
self.bot = bot self.bot = bot
@ -319,7 +304,11 @@ class SecureValue(TelegramObject):
selfie (:class:`telegram.FileCredentials`, optional): Credentials for encrypted selfie selfie (:class:`telegram.FileCredentials`, optional): Credentials for encrypted selfie
of the user with a document. Can be available for "passport", "driver_license", of the user with a document. Can be available for "passport", "driver_license",
"identity_card" and "internal_passport". "identity_card" and "internal_passport".
files (:class:`telegram.Array of FileCredentials`, optional): Credentials for encrypted translation (List[:class:`telegram.FileCredentials`], optional): Credentials for an
encrypted translation of the document. Available for "passport", "driver_license",
"identity_card", "internal_passport", "utility_bill", "bank_statement",
"rental_agreement", "passport_registration" and "temporary_registration".
files (List[:class:`telegram.FileCredentials`], optional): Credentials for encrypted
files. Available for "utility_bill", "bank_statement", "rental_agreement", files. Available for "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types. "passport_registration" and "temporary_registration" types.
@ -331,6 +320,7 @@ class SecureValue(TelegramObject):
reverse_side=None, reverse_side=None,
selfie=None, selfie=None,
files=None, files=None,
translation=None,
bot=None, bot=None,
**kwargs): **kwargs):
self.data = data self.data = data
@ -338,6 +328,7 @@ class SecureValue(TelegramObject):
self.reverse_side = reverse_side self.reverse_side = reverse_side
self.selfie = selfie self.selfie = selfie
self.files = files self.files = files
self.translation = translation
self.bot = bot self.bot = bot
@ -351,6 +342,7 @@ class SecureValue(TelegramObject):
data['reverse_side'] = FileCredentials.de_json(data.get('reverse_side'), bot=bot) data['reverse_side'] = FileCredentials.de_json(data.get('reverse_side'), bot=bot)
data['selfie'] = FileCredentials.de_json(data.get('selfie'), bot=bot) data['selfie'] = FileCredentials.de_json(data.get('selfie'), bot=bot)
data['files'] = FileCredentials.de_list(data.get('files'), bot=bot) data['files'] = FileCredentials.de_list(data.get('files'), bot=bot)
data['translation'] = FileCredentials.de_list(data.get('translation'), bot=bot)
return cls(bot=bot, **data) return cls(bot=bot, **data)
@ -358,6 +350,7 @@ class SecureValue(TelegramObject):
data = super(SecureValue, self).to_dict() data = super(SecureValue, self).to_dict()
data['files'] = [p.to_dict() for p in self.files] data['files'] = [p.to_dict() for p in self.files]
data['translation'] = [p.to_dict() for p in self.translation]
return data return data

View file

@ -25,23 +25,33 @@ class PersonalDetails(TelegramObject):
Attributes: Attributes:
first_name (:obj:`str`): First Name. first_name (:obj:`str`): First Name.
middle_name (:obj:`str`): Optional. First Name.
last_name (:obj:`str`): Last Name. last_name (:obj:`str`): Last Name.
birth_date (:obj:`str`): Date of birth in DD.MM.YYYY format. birth_date (:obj:`str`): Date of birth in DD.MM.YYYY format.
gender (:obj:`str`): Gender, male or female. gender (:obj:`str`): Gender, male or female.
country_code (:obj:`str`): Citizenship (ISO 3166-1 alpha-2 country code). country_code (:obj:`str`): Citizenship (ISO 3166-1 alpha-2 country code).
residence_country_code (:obj:`str`): Country of residence (ISO 3166-1 alpha-2 country residence_country_code (:obj:`str`): Country of residence (ISO 3166-1 alpha-2 country
code). code).
first_name (:obj:`str`): First Name in the language of the user's country of residence.
middle_name (:obj:`str`): Optional. Middle Name in the language of the user's country of
residence.
last_name (:obj:`str`): Last Name in the language of the user's country of residence.
""" """
def __init__(self, first_name, last_name, birth_date, gender, country_code, def __init__(self, first_name, last_name, birth_date, gender, country_code,
residence_country_code, bot=None, **kwargs): residence_country_code, first_name_native, last_name_native, middle_name=None,
middle_name_native=None, bot=None, **kwargs):
# Required # Required
self.first_name = first_name self.first_name = first_name
self.last_name = last_name self.last_name = last_name
self.middle_name = middle_name
self.birth_date = birth_date self.birth_date = birth_date
self.gender = gender self.gender = gender
self.country_code = country_code self.country_code = country_code
self.residence_country_code = residence_country_code self.residence_country_code = residence_country_code
self.first_name_native = first_name_native
self.last_name_native = last_name_native
self.middle_name_native = middle_name_native
self.bot = bot self.bot = bot

View file

@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram EncryptedPassportElement.""" """This module contains an object that represents a Telegram EncryptedPassportElement."""
from base64 import b64decode
from telegram import (TelegramObject, PassportFile, PersonalDetails, IdDocumentData, from telegram import (TelegramObject, PassportFile, PersonalDetails, IdDocumentData,
ResidentialAddress) ResidentialAddress)
@ -43,15 +44,22 @@ class EncryptedPassportElement(TelegramObject):
files (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted files files (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted files
with documents provided by the user, available for "utility_bill", "bank_statement", with documents provided by the user, available for "utility_bill", "bank_statement",
"rental_agreement", "passport_registration" and "temporary_registration" types. "rental_agreement", "passport_registration" and "temporary_registration" types.
front_side (:class:`PassportFile`): Optional. Encrypted/decrypted file with the front side front_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
of the document, provided by the user. Available for "passport", "driver_license", front side of the document, provided by the user. Available for "passport",
"identity_card" and "internal_passport".
reverse_side (:class:`PassportFile`): Optional. Encrypted/decrypted file with the reverse
side of the document, provided by the user. Available for "driver_license" and
"identity_card".
selfie (:class:`PassportFile`): Optional. Encrypted/decrypted file with the selfie of the
user holding a document, provided by the user; available for "passport",
"driver_license", "identity_card" and "internal_passport". "driver_license", "identity_card" and "internal_passport".
reverse_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
reverse side of the document, provided by the user. Available for "driver_license" and
"identity_card".
selfie (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
selfie of the user holding a document, provided by the user; available for "passport",
"driver_license", "identity_card" and "internal_passport".
translation (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted
files with translated versions of documents provided by the user. Available if
requested for "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
hash (:obj:`str`): Base64-encoded element hash for using in
:class:`telegram.PassportElementErrorUnspecified`.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args: Args:
@ -69,15 +77,22 @@ class EncryptedPassportElement(TelegramObject):
files (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted files files (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted files
with documents provided by the user, available for "utility_bill", "bank_statement", with documents provided by the user, available for "utility_bill", "bank_statement",
"rental_agreement", "passport_registration" and "temporary_registration" types. "rental_agreement", "passport_registration" and "temporary_registration" types.
front_side (:class:`PassportFile`, optional): Encrypted/decrypted file with the front side front_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
of the document, provided by the user. Available for "passport", "driver_license", front side of the document, provided by the user. Available for "passport",
"identity_card" and "internal_passport".
reverse_side (:class:`PassportFile`, optional): Encrypted/decrypted file with the reverse
side of the document, provided by the user. Available for "driver_license" and
"identity_card".
selfie (:class:`PassportFile`, optional): Encrypted/decrypted file with the selfie of the
user holding a document, provided by the user; available for "passport",
"driver_license", "identity_card" and "internal_passport". "driver_license", "identity_card" and "internal_passport".
reverse_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
reverse side of the document, provided by the user. Available for "driver_license" and
"identity_card".
selfie (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
selfie of the user holding a document, provided by the user; available for "passport",
"driver_license", "identity_card" and "internal_passport".
translation (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted
files with translated versions of documents provided by the user. Available if
requested for "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
hash (:obj:`str`): Base64-encoded element hash for using in
:class:`telegram.PassportElementErrorUnspecified`.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
@ -95,6 +110,8 @@ class EncryptedPassportElement(TelegramObject):
front_side=None, front_side=None,
reverse_side=None, reverse_side=None,
selfie=None, selfie=None,
translation=None,
hash=None,
bot=None, bot=None,
credentials=None, credentials=None,
**kwargs): **kwargs):
@ -108,6 +125,8 @@ class EncryptedPassportElement(TelegramObject):
self.front_side = front_side self.front_side = front_side
self.reverse_side = reverse_side self.reverse_side = reverse_side
self.selfie = selfie self.selfie = selfie
self.translation = translation
self.hash = hash
self._id_attrs = (self.type, self.data, self.phone_number, self.email, self.files, self._id_attrs = (self.type, self.data, self.phone_number, self.email, self.files,
self.front_side, self.reverse_side, self.selfie) self.front_side, self.reverse_side, self.selfie)
@ -125,6 +144,7 @@ class EncryptedPassportElement(TelegramObject):
data['front_side'] = PassportFile.de_json(data.get('front_side'), bot) data['front_side'] = PassportFile.de_json(data.get('front_side'), bot)
data['reverse_side'] = PassportFile.de_json(data.get('reverse_side'), bot) data['reverse_side'] = PassportFile.de_json(data.get('reverse_side'), bot)
data['selfie'] = PassportFile.de_json(data.get('selfie'), bot) data['selfie'] = PassportFile.de_json(data.get('selfie'), bot)
data['translation'] = PassportFile.de_list(data.get('translation'), bot) or None
return cls(bot=bot, **data) return cls(bot=bot, **data)
@ -141,9 +161,9 @@ class EncryptedPassportElement(TelegramObject):
if secure_data.data is not None: if secure_data.data is not None:
# If not already decrypted # If not already decrypted
if not isinstance(data['data'], dict): if not isinstance(data['data'], dict):
data['data'] = decrypt_json(secure_data.data.secret, data['data'] = decrypt_json(b64decode(secure_data.data.secret),
secure_data.data.hash, b64decode(secure_data.data.hash),
data['data']) b64decode(data['data']))
if data['type'] == 'personal_details': if data['type'] == 'personal_details':
data['data'] = PersonalDetails.de_json(data['data'], bot=bot) data['data'] = PersonalDetails.de_json(data['data'], bot=bot)
elif data['type'] in ('passport', 'internal_passport', elif data['type'] in ('passport', 'internal_passport',
@ -153,13 +173,15 @@ class EncryptedPassportElement(TelegramObject):
data['data'] = ResidentialAddress.de_json(data['data'], bot=bot) data['data'] = ResidentialAddress.de_json(data['data'], bot=bot)
data['files'] = PassportFile.de_list_decrypted(data.get('files'), bot, data['files'] = PassportFile.de_list_decrypted(data.get('files'), bot,
secure_data) or None secure_data.files) or None
data['front_side'] = PassportFile.de_json_decrypted(data.get('front_side'), bot, data['front_side'] = PassportFile.de_json_decrypted(data.get('front_side'), bot,
secure_data.front_side) secure_data.front_side)
data['reverse_side'] = PassportFile.de_json_decrypted(data.get('reverse_side'), bot, data['reverse_side'] = PassportFile.de_json_decrypted(data.get('reverse_side'), bot,
secure_data.reverse_side) secure_data.reverse_side)
data['selfie'] = PassportFile.de_json_decrypted(data.get('selfie'), bot, data['selfie'] = PassportFile.de_json_decrypted(data.get('selfie'), bot,
secure_data.selfie) secure_data.selfie)
data['translation'] = PassportFile.de_list_decrypted(data.get('translation'), bot,
secure_data.translation) or None
return cls(bot=bot, **data) return cls(bot=bot, **data)
@ -179,5 +201,7 @@ class EncryptedPassportElement(TelegramObject):
if self.files: if self.files:
data['files'] = [p.to_dict() for p in self.files] data['files'] = [p.to_dict() for p in self.files]
if self.translation:
data['translation'] = [p.to_dict() for p in self.translation]
return data return data

View file

@ -250,3 +250,108 @@ class PassportElementErrorSelfie(PassportElementError):
self.file_hash = file_hash self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message) self._id_attrs = (self.source, self.type, self.file_hash, self.message)
class PassportElementErrorTranslationFile(PassportElementError):
"""
Represents an issue with one of the files that constitute the translation of a document.
The error is considered resolved when the file changes.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration".
file_hash (:obj:`str`): Base64-encoded hash of the file.
message (:obj:`str`): Error message.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration".
file_hash (:obj:`str`): Base64-encoded hash of the file.
message (:obj:`str`): Error message.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
def __init__(self,
type,
file_hash,
message,
**kwargs):
# Required
super(PassportElementErrorTranslationFile, self).__init__('translation_file',
type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
class PassportElementErrorTranslationFiles(PassportElementError):
"""
Represents an issue with the translated version of a document. The error is considered
resolved when a file with the document translation change.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration"
file_hash (:obj:`str`): Base64-encoded file hash.
message (:obj:`str`): Error message.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of "passport", "driver_license", "identity_card", "internal_passport",
"utility_bill", "bank_statement", "rental_agreement", "passport_registration",
"temporary_registration"
file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
message (:obj:`str`): Error message.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
def __init__(self,
type,
file_hashes,
message,
**kwargs):
# Required
super(PassportElementErrorTranslationFiles, self).__init__('translation_files',
type, message)
self.file_hashes = file_hashes
self._id_attrs = ((self.source, self.type, self.message) +
tuple([file_hash for file_hash in file_hashes]))
class PassportElementErrorUnspecified(PassportElementError):
"""
Represents an issue in an unspecified place. The error is considered resolved when new
data is added.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue.
element_hash (:obj:`str`): Base64-encoded element hash.
message (:obj:`str`): Error message.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue.
element_hash (:obj:`str`): Base64-encoded element hash.
message (:obj:`str`): Error message.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
def __init__(self,
type,
element_hash,
message,
**kwargs):
# Required
super(PassportElementErrorUnspecified, self).__init__('unspecified', type, message)
self.element_hash = element_hash
self._id_attrs = (self.source, self.type, self.element_hash, self.message)

View file

@ -84,7 +84,7 @@ class PassportFile(TelegramObject):
if not data: if not data:
return [] return []
return [cls.de_json_decrypted(passport_file, bot, credentials.files[i]) return [cls.de_json_decrypted(passport_file, bot, credentials[i])
for i, passport_file in enumerate(data)] for i, passport_file in enumerate(data)]
def get_file(self, timeout=None, **kwargs): def get_file(self, timeout=None, **kwargs):

View file

@ -22,29 +22,63 @@ import pytest
from telegram import (PassportData, PassportFile, Bot, File, PassportElementErrorSelfie, from telegram import (PassportData, PassportFile, Bot, File, PassportElementErrorSelfie,
PassportElementErrorDataField, Credentials, TelegramDecryptionError) PassportElementErrorDataField, Credentials, TelegramDecryptionError)
# Generated using the scope:
RAW_PASSPORT_DATA = {'data': [{'type': 'personal_details', # {
'data': 'tj3pNwOpN+ZHsyb6F3aJcNmEyPxrOtGTbu3waBlCQDNaQ9oJlkbXpw+HI3y9faq/+TCeB/WsS/2TxRXTKZw4zXvGP2UsfdRkJ2SQq6x+Ffe/oTF9/q8sWp2BwU3hHUOz7ec1/QrdPBhPJjbwSykEBNggPweiBVDZ0x/DWJ0guCkGT9smYGqog1vqlqbIWG7AWcxVy2fpUy9w/zDXjxj5WQ3lRpHJmi46s9xIHobNGGBvWw6/bGBCInMoovgqRCEu1sgz2QXF3wNiUzGFycEzLz7o+1htLys5n8Pdi9MG4RY='}, # data: [
{'type': 'driver_license', # {
'data': 'hOXQ/iHSGRDFXqql3yETA4AiP0mdlwmo9RtGS+Qg9E5okrN/yTcPNtBKb2fLA0posk35bvevN53cyJMHZnxErEFTSw/FQjPyRFdJUyjGNPeu4yOI73uk5eRVLTAlA2G0eN2azzfS/QOBGL19N3pHk9hMTZYPCBTDt89MHhRQS7Z3YWRSzFcFiEhktHv/ezgcg3EWtsUQ8K4J2445uoZzbB8wsQ6RM4ibn08RfjV6dNyVrj8jBGUpCBlA6iY60rFQM+LZ9ByI3OeS53bnIAMQC2rHHbV/wkzS6PbufOzjZgJq26aCLmC5YDomrpcrdvk0fvi6aEuBJEI3zcteh2fh/Q==', # type: 'personal_details',
'selfie': {'file_id': 'DgADBAADEQQAAkopgFNr6oi-wISRtAI', # native_names: true
'file_date': 1534074942}, # },
'reverse_side': {'file_id': 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI', # {
'file_date': 1534074942}, # type: 'id_document',
'front_side': {'file_id': 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI', # selfie: true,
'file_date': 1534074942}}, # translation: true
{'type': 'address', # },
'data': 'j9SksVkSj128DBtZA+3aNjSFNirzv+R97guZaMgae4Gi0oDVNAF7twPR7j9VSmPedfJrEwL3O889Ei+a5F1xyLLyEI/qEBljvL70GFIhYGitS0JmNabHPHSZrjOl8b4s/0Z0Px2GpLO5siusTLQonimdUvu4UPjKquYISmlKEKhtmGATy+h+JDjNCYuOkhakeNw0Rk0BHgj0C3fCb7WZNQSyVb+2GTu6caR6eXf/AFwFp0TV3sRz3h0WIVPW8bna'}, # {
{'type': 'utility_bill', 'files': [ # type: 'address_document',
{'file_id': 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI', # translation: true
'file_date': 1534074988}, # },
{'file_id': 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI', # 'address',
'file_date': 1534074988}]}, # 'email'
{'type': 'email', 'email': 'fb3e3i47zt@dispostable.com'}], # ],
'credentials': { # v: 1
'data': 'uI/g4fJLVO6132t+yuBvKExTpTubinscH8KLVc8YPuo1SiXaBg4A6AaVdv60CPViMw8n+ShVWOTL6oN5Ye0+CC2/URZ0eeTMJcKkvJYRI0Q6YJ3aeeEzslwORKho0mGk5xSWpPV5LHXhRIlgUMA32NkbNuzzoij7OhqxvuDE50/0pAUKtxy69h+heAzxFj8+jYjgnOwgwNa6FGUG59oUozgpB98XrEeWGW+JIGE9fux8dSGkFhF2SjkmdW6b2Gexuq94TVGDgFigSvqMOgZj9slZI4UEdZBIGldPHrc8/+EuqWv+WKWU8hdj1dxfY/pNvonb8MwrQLrUyNafdLq5QD4Kmg3XPnZTI9bsYUf6xO9oKU42JezDcRbaCqKkUn6UceCkV2hHMJP4aVVI9Bad1k/rXlaDh8PUV19n6tct5UH8JfkBbGlj2uASI5lPasIvnWg9s1vsZ4ynE2YAuU4iotMkgmJkj1+JCl8Ul9uqzfiK0IbYfu57V82gUEjn6c4br49pY+Cvo9od9Lx8fSJCiXq+DdmDTfIZxpeFqEYeJi6/v0CjJni4CtS6LJGo9JUR2MlqOAiyOLomadlrZyzC2eLm8X6ouIXTxtbUqzxcTiH/r23NQ44YwJpMdDiulNuPg0tEyio8TokHj9APORiW+QIw4zLZFyBEorT1DSALm4AV3A1lIg0svOncDkOqa3ZydQ0tdysoXOq5Zsu+Qu/DQZQz5IGEHb7N9QW6KZDGfBOP5Ok04OxlXO8UaTZr6mysmu8jDOlqtitLKSCGDy0LPfITzehTqfEu0KrtmxI91zJtiNyE4g+oBzQsLwQ7vSp1xl7YNViDi8ea/upZdRavh3NthVD5TxyNJedt4Xp8q5/H5kSV/EFhumONPwBwOuN4KrsCQEaNtHZfYB8SH1v0rWbiFw3/owxCrCPqgEwMGnx+JTqsAplurIZfxsNz+57O4Hq10VVt7yhroq9UpIAR8C8hW58dcZBflWyQwG8HalYAaPJ/teSLP9o6MyiqrUkADbNBOFsUhIerq6Q0BnQ941sWg10BkG8oQSU6JdnZ1Ygml2gpFabBRXeeF5ijD0Y9Bq8D3vDnHKx/GziAK0aKlwSqfTmJ+C1hm7AxBekjtDavkBeq5bIOtxYaXqrtunQFAZRCrcnQEg0DPY6dYw+jmOR0NuHRMU5KeZ0994Jn0TRGiUoJtLp1MHJ2D1MCWG3Fp9Ps7vB46a9XiGYHJUdJjHsavvQRQjhB1FvA/kxXNubCjAOPLUz1j+BEkUUgwi65l8+pViHUCIhBdLJn7S0J9HbMsc3jXOFusfvXfyvf2SZMqyhEscs/2HzYgbCWVSxQ//6eimZqvW4an1JIEfH1xUH3zS0HvS7NWChtX1Cj+jJ2vw+lyom6a84wJ2mjSRVcaV6JZa2mIf2IuXfX8EMZwKMDJagkQ7effV3MrnCNrXUXJYn8wVm6l+uP+R9mCn9qbGlvWToCID0YM8MTxJ0LgzC0n93m46OxxCI1p1hPzVrmDCcKt3OASe0zVat415/sz9TTU7Rm8oNSAVUVNo7FRg2XwEb9VupGMjlscu4cJKLCtUpImSKDKlVLH2KYy6s4NdPjOjqtjQLRoaCxbYHmNXXE+vVwPiNBK/bBVT3XJgdmbkXa/PDnMjhAmn0huuxBu8GsTOs+bBdyDCbr6mY6EzN9lCPspEoOPs7nmREpjrvGmEqEddRteyAebpVqZQhqklyT3RVl8AmfoF7QgTi0NYqFb50Lgou3hFrMg7isG5EKU+IKI5WfzyWONlHNSpvO', # }
'hash': 'qyi5vQ2HCVjVMHb+l6HQ4krmvwwEkvm6Qmdf5GJcohM=', RAW_PASSPORT_DATA = {'credentials': {'hash': 'qB4hz2LMcXYhglwz6EvXMMyI3PURisWLXl/iCmCXcSk=',
'secret': 'kgJE0VLB9enOq5fhhX8gOv0xoaMIcskmRPOG1eMbiC/Q8slr7kur12H/YIoOfd7/DQ0ggE7TAAe34PypFvtmwt5fDVtqtPl9YoCeAOCFWxHxLgTCLbzoJ0lTJXoJkdHjlvR2lKaP+rMtaU1w8WOpYOGiNXyblQoWwFRrWNTHmHnmwBfGBFCj/vp89+C1viEYHeWPPUkBhf1vT31L70BEoe8hxORJEDg+jY+80W2nFdIWNBF+o9GSmbMWFtd7UFiuLPp2JUBCy8XuHozk8xFk/PN6m6DgSu32rC4YBJv/sWGUo/MmH0nxR3gaiEkj+9rWIybCNAwgfdQpk/KH2RCF8g=='}} 'secret': 'O6x3X2JrLO1lUIhw48os1gaenDuZLhesoZMKXehZwtM3vsxOdtxHKWQyLNwtbyy4snYpARXDwf8f1QHNmQ/M1PwBQvk1ozrZBXb4a6k/iYj+P4v8Xw2M++CRHqZv0LaxFtyHOXnNYZ6dXpNeF0ZvYYTmm0FsYvK+/3/F6VDB3Oe6xWlXFLwaCCP/jA9i2+dKp6iq8NLOo4VnxenPKWWYz20RZ50MdAbS3UR+NCx4AHM2P5DEGrHNW0tMXJ+FG3jpVrit5BuCbB/eRgKxNGRWNxEGV5hun5MChdxKCEHCimnUA97h7MZdoTgYxkrfvDSZ/V89BnFXLdr87t/NLvVE0Q==',
'data': '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'},
'data': [
{
'data': 'QRfzWcCN4WncvRO3lASG+d+c5gzqXtoCinQ1PgtYiZMKXCksx9eB9Ic1bOt8C/un9/XaX220PjJSO7Kuba+nXXC51qTsjqP9rnLKygnEIWjKrfiDdklzgcukpRzFSjiOAvhy86xFJZ1PfPSrFATy/Gp1RydLzbrBd2ZWxZqXrxcMoA0Q2UTTFXDoCYerEAiZoD69i79tB/6nkLBcUUvN5d52gKd/GowvxWqAAmdO6l1N7jlo6aWjdYQNBAK1KHbJdbRZMJLxC1MqMuZXAYrPoYBRKr5xAnxDTmPn/LEZKLc3gwwZyEgR5x7e9jp5heM6IEMmsv3O/6SUeEQs7P0iVuRSPLMJLfDdwns8Tl3fF2M4IxKVovjCaOVW+yHKsADDAYQPzzH2RcrWVD0TP5I64mzpK64BbTOq3qm3Hn51SV9uA/+LvdGbCp7VnzHx4EdUizHsVyilJULOBwvklsrDRvXMiWmh34ZSR6zilh051tMEcRf0I+Oe7pIxVJd/KKfYA2Z/eWVQTCn5gMuAInQNXFSqDIeIqBX+wca6kvOCUOXB7J2uRjTpLaC4DM9s/sNjSBvFixcGAngt+9oap6Y45rQc8ZJaNN/ALqEJAmkphW8=',
'type': 'personal_details'
}, {
'reverse_side': {'file_date': 1534074942,
'file_id': 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI'},
'translation': [{'file_size': 28640, 'file_date': 1535630933,
'file_id': 'DgADBAADswMAAisqQVAmooP-kVgLgAI'},
{'file_size': 28672, 'file_date': 1535630933,
'file_id': 'DgADBAAD1QMAAnrpQFBMZsT3HysjwwI'}],
'front_side': {'file_size': 28624, 'file_date': 1534074942,
'file_id': 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI'},
'type': 'driver_license',
'selfie': {'file_size': 28592, 'file_date': 1534074942,
'file_id': 'DgADBAADEQQAAkopgFNr6oi-wISRtAI'},
'data': 'eJUOFuY53QKmGqmBgVWlLBAQCUQJ79n405SX6M5aGFIIodOPQqnLYvMNqTwTrXGDlW+mVLZcbu+y8luLVO8WsJB/0SB7q5WaXn/IMt1G9lz5G/KMLIZG/x9zlnimsaQLg7u8srG6L4KZzv+xkbbHjZdETrxU8j0N/DoS4HvLMRSJAgeFUrY6v2YW9vSRg+fSxIqQy1jR2VKpzAT8OhOz7A=='
}, {
'translation': [{'file_size': 28480, 'file_date': 1535630939,
'file_id': 'DgADBAADyQUAAqyqQVC_eoX_KwNjJwI'},
{'file_size': 28528, 'file_date': 1535630939,
'file_id': 'DgADBAADsQQAAubTQVDRO_FN3lOwWwI'}],
'files': [{'file_size': 28640, 'file_date': 1534074988,
'file_id': 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI'},
{'file_size': 28480, 'file_date': 1534074988,
'file_id': 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI'}],
'type': 'utility_bill'
}, {
'data': 'j9SksVkSj128DBtZA+3aNjSFNirzv+R97guZaMgae4Gi0oDVNAF7twPR7j9VSmPedfJrEwL3O889Ei+a5F1xyLLyEI/qEBljvL70GFIhYGitS0JmNabHPHSZrjOl8b4s/0Z0Px2GpLO5siusTLQonimdUvu4UPjKquYISmlKEKhtmGATy+h+JDjNCYuOkhakeNw0Rk0BHgj0C3fCb7WZNQSyVb+2GTu6caR6eXf/AFwFp0TV3sRz3h0WIVPW8bna',
'type': 'address'
}, {
'email': 'fb3e3i47zt@dispostable.com', 'type': 'email'
}]}
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
@ -54,33 +88,42 @@ def all_passport_data():
{'type': 'passport', {'type': 'passport',
'data': RAW_PASSPORT_DATA['data'][1]['data'], 'data': RAW_PASSPORT_DATA['data'][1]['data'],
'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'], 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']}, 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
'translation': RAW_PASSPORT_DATA['data'][1]['translation']},
{'type': 'internal_passport', {'type': 'internal_passport',
'data': RAW_PASSPORT_DATA['data'][1]['data'], 'data': RAW_PASSPORT_DATA['data'][1]['data'],
'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'], 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']}, 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
'translation': RAW_PASSPORT_DATA['data'][1]['translation']},
{'type': 'driver_license', {'type': 'driver_license',
'data': RAW_PASSPORT_DATA['data'][1]['data'], 'data': RAW_PASSPORT_DATA['data'][1]['data'],
'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'], 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'], 'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']}, 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
'translation': RAW_PASSPORT_DATA['data'][1]['translation']},
{'type': 'identity_card', {'type': 'identity_card',
'data': RAW_PASSPORT_DATA['data'][1]['data'], 'data': RAW_PASSPORT_DATA['data'][1]['data'],
'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'], 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'], 'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']}, 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie'],
{'type': 'address', 'translation': RAW_PASSPORT_DATA['data'][1]['translation']},
'data': RAW_PASSPORT_DATA['data'][2]['data']},
{'type': 'utility_bill', {'type': 'utility_bill',
'files': RAW_PASSPORT_DATA['data'][3]['files']}, 'files': RAW_PASSPORT_DATA['data'][2]['files'],
'translation': RAW_PASSPORT_DATA['data'][2]['translation']},
{'type': 'bank_statement', {'type': 'bank_statement',
'files': RAW_PASSPORT_DATA['data'][3]['files']}, 'files': RAW_PASSPORT_DATA['data'][2]['files'],
'translation': RAW_PASSPORT_DATA['data'][2]['translation']},
{'type': 'rental_agreement', {'type': 'rental_agreement',
'files': RAW_PASSPORT_DATA['data'][3]['files']}, 'files': RAW_PASSPORT_DATA['data'][2]['files'],
'translation': RAW_PASSPORT_DATA['data'][2]['translation']},
{'type': 'passport_registration', {'type': 'passport_registration',
'files': RAW_PASSPORT_DATA['data'][3]['files']}, 'files': RAW_PASSPORT_DATA['data'][2]['files'],
'translation': RAW_PASSPORT_DATA['data'][2]['translation']},
{'type': 'temporary_registration', {'type': 'temporary_registration',
'files': RAW_PASSPORT_DATA['data'][3]['files']}, 'files': RAW_PASSPORT_DATA['data'][2]['files'],
'translation': RAW_PASSPORT_DATA['data'][2]['translation']},
{'type': 'address',
'data': RAW_PASSPORT_DATA['data'][3]['data']},
{'type': 'email', {'type': 'email',
'email': 'fb3e3i47zt@dispostable.com'}, 'email': 'fb3e3i47zt@dispostable.com'},
{'type': 'phone_number', {'type': 'phone_number',
@ -96,8 +139,12 @@ class TestPassport(object):
driver_license_selfie_file_id = 'DgADBAADEQQAAkopgFNr6oi-wISRtAI' driver_license_selfie_file_id = 'DgADBAADEQQAAkopgFNr6oi-wISRtAI'
driver_license_front_side_file_id = 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI' driver_license_front_side_file_id = 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI'
driver_license_reverse_side_file_id = 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI' driver_license_reverse_side_file_id = 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI'
driver_license_translation_1_file_id = 'DgADBAADswMAAisqQVAmooP-kVgLgAI'
driver_license_translation_2_file_id = 'DgADBAAD1QMAAnrpQFBMZsT3HysjwwI'
utility_bill_1_file_id = 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI' utility_bill_1_file_id = 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI'
utility_bill_2_file_id = 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI' utility_bill_2_file_id = 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI'
utility_bill_translation_1_file_id = 'DgADBAADyQUAAqyqQVC_eoX_KwNjJwI'
utility_bill_translation_2_file_id = 'DgADBAADsQQAAubTQVDRO_FN3lOwWwI'
driver_license_selfie_credentials_file_hash = 'Cila/qLXSBH7DpZFbb5bRZIRxeFW2uv/ulL0u0JNsYI=' driver_license_selfie_credentials_file_hash = 'Cila/qLXSBH7DpZFbb5bRZIRxeFW2uv/ulL0u0JNsYI='
driver_license_selfie_credentials_secret = 'tivdId6RNYNsvXYPppdzrbxOBuBOr9wXRPDcCvnXU7E=' driver_license_selfie_credentials_secret = 'tivdId6RNYNsvXYPppdzrbxOBuBOr9wXRPDcCvnXU7E='
@ -105,7 +152,7 @@ class TestPassport(object):
assert isinstance(passport_data, PassportData) assert isinstance(passport_data, PassportData)
def test_expected_encrypted_values(self, passport_data): def test_expected_encrypted_values(self, passport_data):
personal_details, driver_license, address, utility_bill, email = passport_data.data personal_details, driver_license, utility_bill, address, email = passport_data.data
assert personal_details.type == 'personal_details' assert personal_details.type == 'personal_details'
assert personal_details.data == RAW_PASSPORT_DATA['data'][0]['data'] assert personal_details.data == RAW_PASSPORT_DATA['data'][0]['data']
@ -118,29 +165,41 @@ class TestPassport(object):
assert driver_license.front_side.file_id == self.driver_license_front_side_file_id assert driver_license.front_side.file_id == self.driver_license_front_side_file_id
assert isinstance(driver_license.reverse_side, PassportFile) assert isinstance(driver_license.reverse_side, PassportFile)
assert driver_license.reverse_side.file_id == self.driver_license_reverse_side_file_id assert driver_license.reverse_side.file_id == self.driver_license_reverse_side_file_id
assert isinstance(driver_license.translation[0], PassportFile)
assert address.type == 'address' assert driver_license.translation[0].file_id == self.driver_license_translation_1_file_id
assert address.data == RAW_PASSPORT_DATA['data'][2]['data'] assert isinstance(driver_license.translation[1], PassportFile)
assert driver_license.translation[1].file_id == self.driver_license_translation_2_file_id
assert utility_bill.type == 'utility_bill' assert utility_bill.type == 'utility_bill'
assert isinstance(utility_bill.files[0], PassportFile) assert isinstance(utility_bill.files[0], PassportFile)
assert utility_bill.files[0].file_id == self.utility_bill_1_file_id assert utility_bill.files[0].file_id == self.utility_bill_1_file_id
assert isinstance(utility_bill.files[1], PassportFile) assert isinstance(utility_bill.files[1], PassportFile)
assert utility_bill.files[1].file_id == self.utility_bill_2_file_id assert utility_bill.files[1].file_id == self.utility_bill_2_file_id
assert isinstance(utility_bill.translation[0], PassportFile)
assert utility_bill.translation[0].file_id == self.utility_bill_translation_1_file_id
assert isinstance(utility_bill.translation[1], PassportFile)
assert utility_bill.translation[1].file_id == self.utility_bill_translation_2_file_id
assert address.type == 'address'
assert address.data == RAW_PASSPORT_DATA['data'][3]['data']
assert email.type == 'email' assert email.type == 'email'
assert email.email == 'fb3e3i47zt@dispostable.com' assert email.email == 'fb3e3i47zt@dispostable.com'
def test_expected_decrypted_values(self, passport_data): def test_expected_decrypted_values(self, passport_data):
(personal_details, driver_license, address, (personal_details, driver_license, utility_bill, address,
utility_bill, email) = passport_data.decrypted_data email) = passport_data.decrypted_data
assert personal_details.type == 'personal_details' assert personal_details.type == 'personal_details'
assert personal_details.data.to_dict() == {'gender': 'female', assert personal_details.data.to_dict() == {'first_name': 'FIRSTNAME',
'middle_name': 'MIDDLENAME',
'first_name_native': 'FIRSTNAMENATIVE',
'residence_country_code': 'DK', 'residence_country_code': 'DK',
'country_code': 'DK',
'birth_date': '01.01.2001', 'birth_date': '01.01.2001',
'first_name': 'FIRSTNAME', 'last_name_native': 'LASTNAMENATIVE',
'gender': 'female',
'middle_name_native': 'MIDDLENAMENATIVE',
'country_code': 'DK',
'last_name': 'LASTNAME'} 'last_name': 'LASTNAME'}
assert driver_license.type == 'driver_license' assert driver_license.type == 'driver_license'
@ -210,7 +269,7 @@ class TestPassport(object):
def test_wrong_hash(self, bot): def test_wrong_hash(self, bot):
data = deepcopy(RAW_PASSPORT_DATA) data = deepcopy(RAW_PASSPORT_DATA)
data['credentials']['hash'] = b'notcorrecthash' data['credentials']['hash'] = 'bm90Y29ycmVjdGhhc2g=' # Not correct hash
passport_data = PassportData.de_json(data, bot=bot) passport_data = PassportData.de_json(data, bot=bot)
with pytest.raises(TelegramDecryptionError): with pytest.raises(TelegramDecryptionError):
assert passport_data.decrypted_data assert passport_data.decrypted_data

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import PassportElementErrorTranslationFile, PassportElementErrorDataField
@pytest.fixture(scope='class')
def passport_element_error_translation_file():
return PassportElementErrorTranslationFile(TestPassportElementErrorTranslationFile.type,
TestPassportElementErrorTranslationFile.file_hash,
TestPassportElementErrorTranslationFile.message)
class TestPassportElementErrorTranslationFile(object):
source = 'translation_file'
type = 'test_type'
file_hash = 'file_hash'
message = 'Error message'
def test_expected_values(self, passport_element_error_translation_file):
assert passport_element_error_translation_file.source == self.source
assert passport_element_error_translation_file.type == self.type
assert passport_element_error_translation_file.file_hash == self.file_hash
assert passport_element_error_translation_file.message == self.message
def test_to_dict(self, passport_element_error_translation_file):
passport_element_error_translation_file_dict = \
passport_element_error_translation_file.to_dict()
assert isinstance(passport_element_error_translation_file_dict, dict)
assert (passport_element_error_translation_file_dict['source'] ==
passport_element_error_translation_file.source)
assert (passport_element_error_translation_file_dict['type'] ==
passport_element_error_translation_file.type)
assert (passport_element_error_translation_file_dict['file_hash'] ==
passport_element_error_translation_file.file_hash)
assert (passport_element_error_translation_file_dict['message'] ==
passport_element_error_translation_file.message)
def test_equality(self):
a = PassportElementErrorTranslationFile(self.type, self.file_hash, self.message)
b = PassportElementErrorTranslationFile(self.type, self.file_hash, self.message)
c = PassportElementErrorTranslationFile(self.type, '', '')
d = PassportElementErrorTranslationFile('', self.file_hash, '')
e = PassportElementErrorTranslationFile('', '', self.message)
f = PassportElementErrorDataField(self.type, '', '', self.message)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import PassportElementErrorTranslationFiles, PassportElementErrorSelfie
@pytest.fixture(scope='class')
def passport_element_error_translation_files():
return PassportElementErrorTranslationFiles(TestPassportElementErrorTranslationFiles.type,
TestPassportElementErrorTranslationFiles.file_hashes, # flake8: noqa
TestPassportElementErrorTranslationFiles.message)
class TestPassportElementErrorTranslationFiles(object):
source = 'translation_files'
type = 'test_type'
file_hashes = ['hash1', 'hash2']
message = 'Error message'
def test_expected_values(self, passport_element_error_translation_files):
assert passport_element_error_translation_files.source == self.source
assert passport_element_error_translation_files.type == self.type
assert isinstance(passport_element_error_translation_files.file_hashes, list)
assert passport_element_error_translation_files.file_hashes == self.file_hashes
assert passport_element_error_translation_files.message == self.message
def test_to_dict(self, passport_element_error_translation_files):
passport_element_error_translation_files_dict = \
passport_element_error_translation_files.to_dict()
assert isinstance(passport_element_error_translation_files_dict, dict)
assert (passport_element_error_translation_files_dict['source'] ==
passport_element_error_translation_files.source)
assert (passport_element_error_translation_files_dict['type'] ==
passport_element_error_translation_files.type)
assert (passport_element_error_translation_files_dict['file_hashes'] ==
passport_element_error_translation_files.file_hashes)
assert (passport_element_error_translation_files_dict['message'] ==
passport_element_error_translation_files.message)
def test_equality(self):
a = PassportElementErrorTranslationFiles(self.type, self.file_hashes, self.message)
b = PassportElementErrorTranslationFiles(self.type, self.file_hashes, self.message)
c = PassportElementErrorTranslationFiles(self.type, '', '')
d = PassportElementErrorTranslationFiles('', self.file_hashes, '')
e = PassportElementErrorTranslationFiles('', '', self.message)
f = PassportElementErrorSelfie(self.type, '', self.message)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import PassportElementErrorUnspecified, PassportElementErrorDataField
@pytest.fixture(scope='class')
def passport_element_error_unspecified():
return PassportElementErrorUnspecified(TestPassportElementErrorUnspecified.type,
TestPassportElementErrorUnspecified.element_hash,
TestPassportElementErrorUnspecified.message)
class TestPassportElementErrorUnspecified(object):
source = 'unspecified'
type = 'test_type'
element_hash = 'element_hash'
message = 'Error message'
def test_expected_values(self, passport_element_error_unspecified):
assert passport_element_error_unspecified.source == self.source
assert passport_element_error_unspecified.type == self.type
assert passport_element_error_unspecified.element_hash == self.element_hash
assert passport_element_error_unspecified.message == self.message
def test_to_dict(self, passport_element_error_unspecified):
passport_element_error_unspecified_dict = passport_element_error_unspecified.to_dict()
assert isinstance(passport_element_error_unspecified_dict, dict)
assert (passport_element_error_unspecified_dict['source'] ==
passport_element_error_unspecified.source)
assert (passport_element_error_unspecified_dict['type'] ==
passport_element_error_unspecified.type)
assert (passport_element_error_unspecified_dict['element_hash'] ==
passport_element_error_unspecified.element_hash)
assert (passport_element_error_unspecified_dict['message'] ==
passport_element_error_unspecified.message)
def test_equality(self):
a = PassportElementErrorUnspecified(self.type, self.element_hash, self.message)
b = PassportElementErrorUnspecified(self.type, self.element_hash, self.message)
c = PassportElementErrorUnspecified(self.type, '', '')
d = PassportElementErrorUnspecified('', self.element_hash, '')
e = PassportElementErrorUnspecified('', '', self.message)
f = PassportElementErrorDataField(self.type, '', '', self.message)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)