Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Arbitrary callback data#1844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Bibo-Joshi merged 54 commits intomasterfromarbitrary-callback-data
Jun 6, 2021
Merged

Arbitrary callback data#1844

Bibo-Joshi merged 54 commits intomasterfromarbitrary-callback-data
Jun 6, 2021

Conversation

Bibo-Joshi
Copy link
Member

@Bibo-JoshiBibo-Joshi commentedMar 23, 2020
edited
Loading

This description is quite outdated, please see the discussion below

ToDo

  • When merged, whe should create a wiki page
  • Open an issue for the v14 milestone as reminder to makePersistence.store_callback_data default toTrue and makeBP.get/update_callback_data abstract methods. Currently they're not b/c backwards compatibility …
  • Add/Update.. versionadded:: directives
  • Add a note toDeprecation: v15 #2347 to removeDefaults fromtg.Bot completey and move them totg.ext.Bot. We may have to think about how to handle signatures withDefaultVaule, then, i.e. if_insert_defaults should stay intg.Bot or should be moved totg.ext.Bot.
  • should probably be merged afterCustomize context #2262, so that we can update the added persistence logic according toCustomize context #2262 (comment), see also39a9f83
  • check that the "edit on GH" thingies redirect to the correct file

This PR adds the functionality to

  • pass arbitrary objects ascallback_data toInlineKeyboardButtons
  • in accordance to that, thepattern argument forCallbackQueryHandler may now be a callable accepting thecallback_data as argument.
  • validate that thecallback_data received in an update is the same as the sent one

This is opt-in, so we're not forcing anyone to use it.
Closes#709

How does it work

Passing arbitrary objects ascallback_data

This is achieved by replacing them by theirid() and keeping a dictionary that maps the ids to the objects. I decided to useid() overuuid or similar because

  • Python guarantees that theid() is unique, so that's covered already and
  • immutables won't be stored multiple times. Since most callback data will probably still consist of strings, this seemed clever to me.

Of course, this ties in with persistence. As for the roles PR#1789: Adding stuff to persistence should be reserverd for major features. IMHO, this is one of them.

Signing the data

The callback data sent by the bot will readsignature data.signature is generated usinghmac from the standard library, where the key consists of the bots token and username and the message consits of thedata and thechat_id, the message was sent to.

Upon receiving an update,CallbackQuery.de_json will check, that the data is valid by checking the signature. Malicious updates will be discarded and logged.

Generating the signature and validation done by methods added to thehelpers module.

Users can decide, not to validate the data. This can be useful, when the token has been revoked and they want the old buttons to still work.

Wiki Page: Callback Data

Arbitrary objects ascallback_data

Telegrams Bot API only accepts strings with length up to 64 bytes ascallback_data forInlineKeyboardButtons, which sometimes is quite a limitation.

With PTB you are able, to passany object ascallback_data. This is achieved by storing the object and passing a unique id to Telegram. When aCallbackQuery is recieved, the id in thecallback_data is replaced with the stored object. To use this feature, set the parameterarbitrary_callback_data for yourUpdater orBot instance toTrue.

This means two thigs for you:

  1. If you don't use persistence, buttons won't work after restarting your bot, as the stored updates are lost. More precicely, thecallback_data you will recieve isNone. If you don't need persistence otherwise, you can setstore_callback_data toTrue and all the others toFalse.

  2. When using theCallbackQueryHandler, thepattern argument can now be either

    • a regex expression, which will be used, if thecallback_data is in fact a string
    • a callable accepting thecallback_data as only argument. You can perform any kinds of tests on thecallback_data and returnTrue oreFalse accordingsly

Security of InlineKeyboardButtons

Callback updates are not sent by Telegram, but by the client. This means that they can be manipulated by a user. (While Telegram inofficially does try to prevent this, they don't want Bot devs to rely on them doing the security checks).

Most of the time, this is not really a problem, sincecallback_data often just isYes,No, etc. However, if the callback data is something likedelete message_id 123, the malicious user could delete any message sent by the bot.

To increase security of callbacks, PTB signs outgoing callback data. The signature not only makes sure that thecallback_data is valid, but also that the update comes from the chat, the message with theInlineKeyboardButton was sent in. Incoming updates with an invalid signature will be rejected.

The signature is based on your bots token and username. This means that old buttons will no longer be accepted, if you revoke your token. If you need to revoke your token, e.g. during developement, and want the buttons to still work, you can pass

validate_callback_data=False

to either yourUpdater orBot instance. Make sure to switch it toTrue after some time.

@Bibo-JoshiBibo-Joshiforce-pushed thearbitrary-callback-data branch from5caf64e toca2fbb5CompareApril 15, 2020 10:44
@Bibo-Joshi
Copy link
MemberAuthor

IISC this doesn't handle inline keyboards attached to inline result messages yet. If we revisit this PR, that's one issue to think about

@Bibo-JoshiBibo-Joshi added this to thev13.2 milestoneDec 17, 2020
@Bibo-Joshi
Copy link
MemberAuthor

Bibo-Joshi commentedJan 2, 2021
edited
Loading

We just discussed this and will be going forward with it. A few notes:

  • Switch to UUID instead ofid()
  • built the data storage as LRU cache limiting memory usage
    • store the timestamp along with the timestamp so if the user passesmaxsize=None they can take care of deleting based on time stamps on their own.
    • Still provideCallbackQuery/Context/Bot.drop_callback_data for fine grained control

sketch for a LRU data class:

classLRUDict:data= {}maxsize=1000defput(self,key,object):iflen(self.data)==maxsize:self.data.pop(next(iter(self.data)))# dicts are only guaranteed to be ordered since py3.7, so maybe use OrderedDict instead …self.data[key]=object

Edit: Couple with linked list to make efficient

@tsnoamTelegram GithubBot Revised
Copy link
Member

@Bibo-Joshi Ordered dict is not LRU.
Ordered dict (in python semantics) refers to the order of insertion.
So it's either to remove and insert again for every object we use or we use a proper LRU.
A proper LRU will be implemented so that every access to a key will cause it to be marked as recently used. This is done internally by keeping a doubly linked list which has the most used entries in the head and least used entries in the tail.

Though, writing this, I think that if the intention is to immediately remove items when they're accessed then an Ordered Dict would be enough. (Though it might be limiting if we change the design)

without tests or pre-commit for now# Conflicts:#telegram/bot.py#telegram/callbackquery.py#telegram/error.py#telegram/ext/basepersistence.py#telegram/ext/callbackqueryhandler.py#telegram/ext/dictpersistence.py#telegram/ext/dispatcher.py#telegram/ext/picklepersistence.py#telegram/ext/updater.py#telegram/utils/helpers.py#telegram/utils/webhookhandler.py#tests/test_bot.py#tests/test_callbackquery.py#tests/test_helpers.py#tests/test_persistence.py#tests/test_updater.py
@Bibo-Joshi
Copy link
MemberAuthor

Merging master was surprisingly easy :D
Adapted the LRU idea. Namings can ofc be changed.
Tests run fine locally, let's see how they perform in CI.

@Bibo-Joshi
Copy link
MemberAuthor

Bibo-Joshi commentedJan 6, 2021
edited
Loading

After internal discussion, using the uuid should be enough, so the latest commit removes the signing procedure.
However I became aware of two flaws in the current setup:

  • the data in the keyboard attached to the incomingCallbackQuery is currently not replaced
  • pressing a button doesnot automatically mean that the keyboard will be deleted. The bot might just callanswer_callback_query(text='hint') to display some info, without changing the message or the keyboard. So we musn't delete the data on incomingCallbackQueries. This means that
    1. CallbackDataCache needs to be properly converted to a LRU-cache by updating the order of the items in the doubly linked queue
    2. We should provide convenience methods to delete the objects corresponding to the incoming query or the complete attached keyboard. Probably samething likeCallbackQuery.{clear_data, clear_keyboard_data},InlineKeyboardMarkup.clear_data,InlineKeyoardButton.clear_data() andMessage.clear_keyboard_data

Edit I: We could think of makingMessage.edit_text/reply_markup/… andCallbackQuery.edit_message_text/reply_markup/… (i.e. everything that drops the keyboard) drop the data automatically 🤔

Edit II: just skipping CQs with invalid data is a bad idea. Users might wanna reply with a prompt telling them that the button no longer works. Rather set the data attribute to the InvalidCallbackData exception or something like that.

Edit III: Inline messages. We don't get the keyboard back when buttons from those are pressed. Idea: instead of making the LRU cache per button, make it per keyboard. we have space for 2 uuids in 64chars anyway. So once we get a CQ, we know all the other buttons/objects that we sent along with it and can make use of Edit I. Ofc when answering an inline query with multiple results that have an inline keyboard, we don't know which one actually gets sent, but that's what the LRU cache is good for.

@Bibo-JoshiBibo-Joshi changed the title Arbitrary, signed callback data Arbitrary callback dataJan 6, 2021
@Bibo-JoshiBibo-Joshi mentioned this pull requestMay 30, 2021
@Bibo-Joshi
Copy link
MemberAuthor

Thanks for the thorough review@harshil21, you found a lot of things to improve 😃 So far I've gone through your comments and marked all as resolved that where either clear or where you reacted with 👍 to my comments. I will get to the TODO section soonish. Regarding the other comments:

There's alsoTTLCachefrom cachetools which would mean that we wouldn't need theaccess_time logic, as we can just use their implementation. Maybe that can help simplifying the code?

Unfortunately this doesn't allow for the entries not to expire :/

Regarding slots, should we make a new branch based on this and then merge here? The implementation detail should be kept separate from this imo. This PR is big enough already

Upsie, read that too late :D already merged that

Finally I think an example for this would help users

stay tuned 🎶

Copy link

@github-actionsgithub-actionsbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hey there. Relax, I am just a little warning for the maintainers to release directly after merging your PR, otherwise we have broken examples and people might get confused :)

@Bibo-Joshi
Copy link
MemberAuthor

I added an example that can be reviewed.
Also I just cherry-picked the stuff from#2262 (comment) + made according adjustments here, so all the open todo items in the description are "clean-up" after merge.
Only thing left from my side is to try & up the coverage a bit when the latest tests are finished

Copy link
Member

@harshil21harshil21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Okay example looks good. However, I noticed one thing while running the example - When a user spam clicks a button (say '1'), it becomes an instance ofInvalidCallbackData, and it fails. Of course when you remove thedrop_callback_data line, this doesn't happen anymore. But maybe such behaviour should be at least documented somewhere?

Also can we remove thecast() in the example and ask mypy to ignore them? makes it a little complicated for beginners.

@Bibo-Joshi
Copy link
MemberAuthor

Bibo-Joshi commentedJun 5, 2021
edited
Loading

Okay example looks good. However, I noticed one thing while running the example - When a user spam clicks a button (say '1'), it becomes an instance ofInvalidCallbackData, and it fails. Of course when you remove thedrop_callback_data line, this doesn't happen anymore. But maybe such behaviour should be at least documented somewhere?

Ah, yes, that's right. In fact it could be considered a feature, see#2539 :D Anyway, I've updated the wiki above as follows:

However, if you want to keep memory usage low, you have
additional options to drop data:

  • on receiving aCallbackQuery, you can callcontext.drop_callback_data(callback_query). This will delete the data associated with the keyboard attached to the message that originated theCallbackQuery. Callingcontext.drop_callback_data is safe in any case where you change the keyboard, i.e.callback_query.edit_message_text/reply_markup/…
    Note: If the user clicks a button more than one time fast enough, but you callcontext.drop_callback_data for the first resultingCallbackQuery, the second one will haveInvalidCallbackData ascallback_data. However, this is usually not a problem, because one only wants one button click anyway.

Note that I also scraped the sentence aboutdrop_callback_data not working for inline messages, b/c that's BS (commit incoming) :D

Also can we remove thecast() in the example and ask mypy to ignore them? makes it a little complicated for beginners.

I'm not too sure about this, tbh. If we dropcast, we'll have to add atype: ignore, which probably also confusing for beginners. I'd like to keep at least the cast in

# Get the data from the callback_data.number,number_list=cast(Tuple[int,List[int]],query.data)

becauseif you use type hinting, you'll have to do that cast in any case when using arbitrary callback data, so I think the example should showcase it. I could live with dropping the other two casts, though …. (edit: I did)

harshil21 reacted with thumbs up emoji

Copy link
Member

@harshil21harshil21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM!

Bibo-Joshi reacted with hooray emojiBibo-Joshi reacted with heart emojiBibo-Joshi reacted with rocket emoji
# Conflicts:#docs/source/telegram.ext.utils.types.rst#telegram/ext/__init__.py#telegram/ext/basepersistence.py#telegram/ext/callbackcontext.py#telegram/ext/conversationhandler.py#telegram/ext/dictpersistence.py#telegram/ext/dispatcher.py#telegram/ext/picklepersistence.py#telegram/ext/updater.py#telegram/ext/utils/types.py#telegram/utils/types.py#tests/test_persistence.py#tests/test_slots.py
@Bibo-JoshiBibo-Joshi merged commit8531a7a intomasterJun 6, 2021
@Bibo-JoshiBibo-Joshi deleted the arbitrary-callback-data branchJune 6, 2021 09:48
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsJun 7, 2021
@Bibo-JoshiBibo-Joshi added 🔌 enhancementpr description: enhancement and removed enhancement labelsNov 3, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@GauthamramRavichandranGauthamramRavichandranGauthamramRavichandran left review comments

@github-actionsgithub-actions[bot]github-actions[bot] left review comments

@recepsirinrecepsirinrecepsirin left review comments

@PoolitzerPoolitzerPoolitzer requested changes

@harshil21harshil21harshil21 approved these changes

Assignees
No one assigned
Labels
🔌 enhancementpr description: enhancement
Projects
None yet
Milestone
v13.6
Development

Successfully merging this pull request may close these issues.

make callbackquery safe
6 participants
@Bibo-Joshi@tsnoam@Poolitzer@GauthamramRavichandran@harshil21@recepsirin

[8]ページ先頭

©2009-2025 Movatter.jp