diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index 85773e30..06bd9467 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -13,7 +13,7 @@ from aiohttp.helpers import sentinel from . import api from ..types import ParseMode, base from ..utils import json -from ..utils.auth_widget import check_token +from ..utils.auth_widget import check_integrity class BaseBot: @@ -266,4 +266,4 @@ class BaseBot: self.parse_mode = None def check_auth_widget(self, data): - return check_token(data, self.__token) + return check_integrity(self.__token, data) diff --git a/aiogram/utils/auth_widget.py b/aiogram/utils/auth_widget.py index b9084eb1..c5b03aa3 100644 --- a/aiogram/utils/auth_widget.py +++ b/aiogram/utils/auth_widget.py @@ -8,7 +8,10 @@ import collections import hashlib import hmac +from aiogram.utils.deprecated import deprecated + +@deprecated('`generate_hash` is outdated, please use `check_signature` or `check_integrity`') def generate_hash(data: dict, token: str) -> str: """ Generate secret hash @@ -24,6 +27,7 @@ def generate_hash(data: dict, token: str) -> str: return hmac.new(secret.digest(), msg.encode('utf-8'), digestmod=hashlib.sha256).hexdigest() +@deprecated('`check_token` helper was renamed to `check_integrity`') def check_token(data: dict, token: str) -> bool: """ Validate auth token @@ -34,3 +38,32 @@ def check_token(data: dict, token: str) -> bool: """ param_hash = data.get('hash', '') or '' return param_hash == generate_hash(data, token) + + +def check_signature(token: str, hash: str, **kwargs) -> bool: + """ + Generate hexadecimal representation + of the HMAC-SHA-256 signature of the data-check-string + with the SHA256 hash of the bot's token used as a secret key + + :param token: + :param hash: + :param kwargs: all params received on auth + :return: + """ + secret = hashlib.sha256(token.encode('utf-8')) + check_string = '\n'.join(map(lambda k: f'{k}={kwargs[k]}', sorted(kwargs))) + hmac_string = hmac.new(secret.digest(), check_string.encode('utf-8'), digestmod=hashlib.sha256).hexdigest() + return hmac_string == hash + + +def check_integrity(token: str, data: dict) -> bool: + """ + Verify the authentication and the integrity + of the data received on user's auth + + :param token: Bot's token + :param data: all data that came on auth + :return: + """ + return check_signature(token, **data) diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py index 8a527420..b88efcd7 100644 --- a/aiogram/utils/deprecated.py +++ b/aiogram/utils/deprecated.py @@ -1,10 +1,11 @@ -import functools +import asyncio import inspect import warnings -import asyncio +import functools +from typing import Callable -def deprecated(reason): +def deprecated(reason) -> Callable: """ This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted diff --git a/tests/test_bot/test_api.py b/tests/test_bot/test_api.py new file mode 100644 index 00000000..c5193bcc --- /dev/null +++ b/tests/test_bot/test_api.py @@ -0,0 +1,18 @@ +import pytest +from aiogram.bot.api import check_token + +from aiogram.utils.exceptions import ValidationError + + +VALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890' +INVALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff 123456789' # Space in token and wrong length + + +class Test_check_token: + + def test_valid(self): + assert check_token(VALID_TOKEN) is True + + def test_invalid_token(self): + with pytest.raises(ValidationError): + check_token(INVALID_TOKEN) diff --git a/tests/test_token.py b/tests/test_token.py deleted file mode 100644 index b8a6087f..00000000 --- a/tests/test_token.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest - -from aiogram.bot import api -from aiogram.utils import auth_widget, exceptions - -VALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890' -INVALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff 123456789' # Space in token and wrong length - -VALID_DATA = { - 'date': 1525385236, - 'first_name': 'Test', - 'last_name': 'User', - 'id': 123456789, - 'username': 'username', - 'hash': '69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b' -} -INVALID_DATA = { - 'date': 1525385237, - 'first_name': 'Test', - 'last_name': 'User', - 'id': 123456789, - 'username': 'username', - 'hash': '69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b' -} - - -def test_valid_token(): - assert api.check_token(VALID_TOKEN) - - -def test_invalid_token(): - with pytest.raises(exceptions.ValidationError): - api.check_token(INVALID_TOKEN) - - -def test_widget(): - assert auth_widget.check_token(VALID_DATA, VALID_TOKEN) - - -def test_invalid_widget_data(): - assert not auth_widget.check_token(INVALID_DATA, VALID_TOKEN) diff --git a/tests/test_utils/test_auth_widget.py b/tests/test_utils/test_auth_widget.py new file mode 100644 index 00000000..8c6f5941 --- /dev/null +++ b/tests/test_utils/test_auth_widget.py @@ -0,0 +1,46 @@ +import pytest + +from aiogram.utils.auth_widget import check_integrity, \ + generate_hash, check_token + +TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11' + + +@pytest.fixture +def data(): + return { + 'id': '42', + 'first_name': 'John', + 'last_name': 'Smith', + 'username': 'username', + 'photo_url': 'https://t.me/i/userpic/320/picname.jpg', + 'auth_date': '1565810688', + 'hash': 'c303db2b5a06fe41d23a9b14f7c545cfc11dcc7473c07c9c5034ae60062461ce', + } + + +def test_generate_hash(data): + res = generate_hash(data, TOKEN) + assert res == data['hash'] + + +class Test_check_token: + """ + This case gonna be deleted + """ + def test_ok(self, data): + assert check_token(data, TOKEN) is True + + def test_fail(self, data): + data.pop('username') + assert check_token(data, TOKEN) is False + + +class Test_check_integrity: + + def test_ok(self, data): + assert check_integrity(TOKEN, data) is True + + def test_fail(self, data): + data.pop('username') + assert check_integrity(TOKEN, data) is False