diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 6184de10..f91f4bdd 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -2,6 +2,7 @@ import asyncio import asyncio.tasks import datetime import functools +import ipaddress import typing from typing import Dict, Optional, Union @@ -25,6 +26,21 @@ WEBHOOK = 'webhook' WEBHOOK_CONNECTION = 'WEBHOOK_CONNECTION' WEBHOOK_REQUEST = 'WEBHOOK_REQUEST' +TELEGRAM_IP_LOWER = ipaddress.IPv4Address('149.154.167.197') +TELEGRAM_IP_UPPER = ipaddress.IPv4Address('149.154.167.233') +LOCALHOST_IP = ipaddress.IPv4Address('127.0.0.1') + + +def _check_ip(ip: str) -> bool: + """ + Check IP in range + + :param ip: + :return: + """ + address = ipaddress.IPv4Address(ip) + return TELEGRAM_IP_LOWER <= address <= TELEGRAM_IP_UPPER or address == LOCALHOST_IP + class WebhookRequestHandler(web.View): """ @@ -78,6 +94,11 @@ class WebhookRequestHandler(web.View): :return: :class:`aiohttp.web.Response` """ + if self.request.app.get('_check_ip', False): + ip_address, accept = self.check_ip() + if not accept: + raise web.HTTPUnauthorized() + context.set_value('TELEGRAM_IP', ip_address) context.update_state({'CALLER': WEBHOOK, WEBHOOK_CONNECTION: True, @@ -164,6 +185,26 @@ class WebhookRequestHandler(web.View): if isinstance(result, BaseResponse): return result + def check_ip(self): + """ + Check client IP. Accept requests only from telegram servers. + + :return: + """ + # For reverse proxy (nginx) + forwarded_for = self.request.headers.get('X-Forwarded-For', None) + if forwarded_for: + return forwarded_for, _check_ip(forwarded_for) + + # For default method + peer_name = self.request.transport.get_extra_info('peername') + if peer_name is not None: + host, _ = peer_name + return host, _check_ip(host) + + # Not allowed and can't get client IP + return None, False + def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH): """ diff --git a/aiogram/utils/executor.py b/aiogram/utils/executor.py index 14a78d77..52658308 100644 --- a/aiogram/utils/executor.py +++ b/aiogram/utils/executor.py @@ -61,7 +61,7 @@ def start_pooling(dispatcher, *, loop=None, skip_updates=False, on_startup=None, def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, on_startup=None, on_shutdown=None, - **kwargs): + check_ip=False, **kwargs): log.warning('Start bot with webhook.') if loop is None: loop = dispatcher.loop @@ -72,8 +72,10 @@ def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None, on_ app['_startup_callback'] = on_startup app['_shutdown_callback'] = on_shutdown app['_skip_updates'] = skip_updates + app['_check_ip'] = check_ip app.on_startup.append(_wh_startup) app.on_shutdown.append(_wh_shutdown) web.run_app(app, loop=loop, **kwargs) + return app