Skip to content

Commit d789d9c

Browse files
committed
Restrict callbacks to the chat they were sent in
1 parent e9e67c8 commit d789d9c

File tree

2 files changed

+60
-35
lines changed

2 files changed

+60
-35
lines changed

botogram/callbacks.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def generate_callback_data():
4545
c = ctx()
4646

4747
name = "%s:%s" % (c.component_name(), callback)
48-
return get_callback_data(c.bot, name, data)
48+
return get_callback_data(c.bot, c.chat(), name, data)
4949

5050
self._content.append({
5151
"text": label,
@@ -105,7 +105,7 @@ def buttons():
105105
return Buttons()
106106

107107

108-
def parse_callback_data(bot, raw):
108+
def parse_callback_data(bot, chat, raw):
109109
"""Parse the callback data generated by botogram and return it"""
110110
raw = raw.encode("utf-8")
111111

@@ -121,7 +121,8 @@ def parse_callback_data(bot, raw):
121121
name = prelude[16:]
122122
data = raw[32:]
123123

124-
if not crypto.compare(crypto.get_hmac(bot, name + data), signature):
124+
correct = get_signature(bot, chat, name, data)
125+
if not crypto.compare(correct, signature):
125126
raise crypto.TamperedMessageError
126127

127128
if data:
@@ -130,7 +131,7 @@ def parse_callback_data(bot, raw):
130131
return name, None
131132

132133

133-
def get_callback_data(bot, name, data=None):
134+
def get_callback_data(bot, chat, name, data=None):
134135
"""Get the callback data for the provided name and data"""
135136
name = hashed_callback_name(name)
136137

@@ -145,12 +146,18 @@ def get_callback_data(bot, name, data=None):
145146
)
146147

147148
# Get the signature of the hook name and data
148-
signature = crypto.get_hmac(bot, name + data)
149+
signature = get_signature(bot, chat, name, data)
149150

150151
# Base64 the signature and the hook name together to save space
151152
return (base64.b64encode(signature + name) + data).decode("utf-8")
152153

153154

155+
def get_signature(bot, chat, name, data):
156+
"""Generate a signature for the provided information"""
157+
chat_id = str(chat.id).encode("utf-8")
158+
return crypto.get_hmac(bot, name + b'\0' + chat_id + b'\0' + data)
159+
160+
154161
def hashed_callback_name(name):
155162
"""Get the hashed name of a callback"""
156163
# Get only the first 8 bytes of the hash to fit it into the payload
@@ -159,8 +166,11 @@ def hashed_callback_name(name):
159166

160167
def process(bot, chains, update):
161168
"""Process a callback sent to the bot"""
169+
chat = update.callback_query.message.chat
170+
raw = update.callback_query._data
171+
162172
try:
163-
name, data = parse_callback_data(bot, update.callback_query._data)
173+
name, data = parse_callback_data(bot, chat, raw)
164174
except crypto.TamperedMessageError:
165175
bot.logger.warn(
166176
"The user tampered with the #%s update's data. Skipped it."

tests/test_callbacks.py

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,50 +22,65 @@
2222

2323
from botogram.callbacks import Buttons, parse_callback_data, get_callback_data
2424
from botogram.callbacks import hashed_callback_name
25+
from botogram.components import Component
26+
from botogram.context import Context
27+
from botogram.hooks import Hook
2528

2629

27-
def test_buttons(bot):
28-
buttons = Buttons(bot)
30+
def test_buttons(bot, sample_update):
31+
component = Component("test")
32+
hook = Hook(lambda: None, component)
33+
34+
buttons = Buttons()
2935
buttons[0].url("test 1", "http://example.com")
3036
buttons[0].callback("test 2", "test_callback")
3137
buttons[3].callback("test 3", "another_callback", "data")
3238
buttons[2].switch_inline_query("test 4")
3339
buttons[2].switch_inline_query("test 5", "wow", current_chat=True)
3440

35-
assert buttons._serialize_attachment() == {
36-
"inline_keyboard": [
37-
[
38-
{"text": "test 1", "url": "http://example.com"},
39-
{
40-
"text": "test 2",
41-
"callback_data": get_callback_data(bot, "test_callback"),
42-
},
43-
],
44-
[
45-
{"text": "test 4", "switch_inline_query": ""},
46-
{"text": "test 5", "switch_inline_query_current_chat": "wow"},
41+
with Context(bot, hook, sample_update):
42+
assert buttons._serialize_attachment() == {
43+
"inline_keyboard": [
44+
[
45+
{"text": "test 1", "url": "http://example.com"},
46+
{
47+
"text": "test 2",
48+
"callback_data": get_callback_data(
49+
bot, sample_update.chat(), "test:test_callback",
50+
),
51+
},
52+
],
53+
[
54+
{"text": "test 4", "switch_inline_query": ""},
55+
{
56+
"text": "test 5",
57+
"switch_inline_query_current_chat": "wow"
58+
},
59+
],
60+
[
61+
{
62+
"text": "test 3",
63+
"callback_data": get_callback_data(
64+
bot, sample_update.chat(), "test:another_callback",
65+
"data",
66+
),
67+
},
68+
],
4769
],
48-
[
49-
{
50-
"text": "test 3",
51-
"callback_data": get_callback_data(
52-
bot, "another_callback", "data"
53-
),
54-
},
55-
],
56-
],
57-
}
70+
}
71+
5872

73+
def test_parse_callback_data(bot, sample_update):
74+
c = sample_update.chat()
5975

60-
def test_parse_callback_data(bot):
61-
raw = get_callback_data(bot, "test_callback", "this is some data!")
62-
assert parse_callback_data(bot, raw) == (
76+
raw = get_callback_data(bot, c, "test_callback", "this is some data!")
77+
assert parse_callback_data(bot, c, raw) == (
6378
hashed_callback_name("test_callback"),
6479
"this is some data!",
6580
)
6681

67-
raw = get_callback_data(bot, "test_callback")
68-
assert parse_callback_data(bot, raw) == (
82+
raw = get_callback_data(bot, c, "test_callback")
83+
assert parse_callback_data(bot, c, raw) == (
6984
hashed_callback_name("test_callback"),
7085
None,
7186
)

0 commit comments

Comments
 (0)