Skip to content

One user gets "Although the app should be installed into this workspace", while other does not. #664

Description

@DataGreed

Reproducible in:

The slack_bolt version

slack-bolt==1.11.1
slack-sdk==3.17.0

(same bug with slack-bolt=1.6.0, I've updated hoping it was resolved)

Python runtime version

Python 3.8.9

OS info

ProductName:	macOS
ProductVersion:	12.3.1
BuildVersion:	21E258
Darwin Kernel Version 21.4.0: Fri Mar 18 00:45:05 PDT 2022; root:xnu-8020.101.4~15/RELEASE_X86_64

Steps to reproduce:

  1. Create Django App that uses OAuth based on a template in this repo
  2. Register app.event("message") listener
  3. Install the app with two users using /slack/install view – by a regular user and a workspace owner
  4. Add a bot to #some-channel.
  5. Post some messages in channel by both users, see app.event("message") listener being triggered properly.
  6. Remove the app by workspace owner through a config page.
  7. Reinstall the app by regular user (not a workspace owner).
  8. Add a bot to #some-channel by regular user.
  9. Post a message by regular user, see app.event("message") listener being triggered properly.
  10. Post a message by workspace owner — get a slack middleware error.

Expected result:

app.event("message") listener being triggered properly for all users when message is posted, not just the one who installed the app again

Actual result:

Workspace owner gets slack midleware error:

Applying slack_bolt.middleware.ssl_check.ssl_check.SslCheck
Applying slack_bolt.middleware.request_verification.request_verification.RequestVerification
Applying slack_bolt.middleware.authorization.multi_teams_authorization.MultiTeamsAuthorization
Sending a request - url: https://www.slack.com/api/auth.test, query_params: {}, body_params: {'team_id': 'REDACTED'}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.8.9 slackclient/3.17.0 Darwin/21.4.0'}
Received the following response - status: 200, headers: {'date': 'Wed, 01 Jun 2022 20:17:16 GMT', 'server': 'Apache', 'x-powered-by': 'HHVM/4.153.1', 'access-control-allow-origin': '*', 'referrer-policy': 'no-referrer', 'x-slack-backend': 'r', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'access-control-allow-headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'access-control-expose-headers': 'x-slack-req-id, retry-after', 'expires': 'Mon, 26 Jul 1997 05:00:00 GMT', 'cache-control': 'private, no-cache, no-store, must-revalidate', 'pragma': 'no-cache', 'x-robots-tag': 'noindex,nofollow', 'x-xss-protection': '0', 'x-content-type-options': 'nosniff', 'x-slack-req-id': '3097ad69461e3cbeb1e9ddb962672219', 'vary': 'Accept-Encoding', 'content-type': 'application/json; charset=utf-8', 'x-envoy-upstream-service-time': '90', 'x-backend': 'main_normal main_bedrock_normal_with_overflow main_canary_with_overflow main_bedrock_canary_with_overflow main_control_with_overflow main_bedrock_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-592u', 'x-slack-shared-secret-outcome': 'no-match', 'via': 'envoy-www-iad-g14r, envoy-edge-lhr-aqga', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match', 'connection': 'close', 'transfer-encoding': 'chunked'}, body: {"ok":false,"error":"invalid_auth"}
The stored bot token for enterprise_id: None team_id: REDACTED is no longer valid. (response: {'ok': False, 'error': 'invalid_auth'})
Although the app should be installed into this workspace, the AuthorizeResult (returned value from authorize) for it was not found.
"POST /slack/events HTTP/1.1" 200 52

Activity

  1. DataGreed commented on Jun 1, 2022

    @DataGreed
    Author

    Interestingly enough, this error has a comment in bolt library, stating:

    # This situation can arise if:
    # * A developer installed the app from the "Install to Workspace" button in Slack app config page
    # * The InstallationStore failed to save or deleted the installation for this workspace
    

    But that's clearly not the case. The app was installed via /slack/install.
    The SlackInstallation is clearly saved since it can be properly used by one of the users.

    It seems like when receiving a message, Bolt tries to match the user who posted a message with a bot_token in SlackInstallation table. In case of the regular user, who reinstalled the app it finds it and in case if a workspace owner, who deleted the app through config page, but didn't reinstall it, Bolt matches it with an old SlackInstallation that is no longer valid and since it does not give any control to the developer, there is no way to tell bolt to use the newer token from the most recent SlackInstallation.

    (Or was bolt supposed to delete SlackInstallation when the workspace owner removed the app somehow? If it's the case it's clearly did not happen and it does not seem to be a very reliable design - since if it was supposed to delete it by receiving some kind of a webhook, and the webhook never fired)

  2. DataGreed commented on Jun 1, 2022

    @DataGreed
    Author

    Also tried inviting a completely new user who didn’t interact with the app in this scenario in slack and adding her in the same channel - event listener gets called without any problems (and she clearly does not have any associated slack installation records in the database).

    the problem with the messages coming from workspace admin still persists.

  3. DataGreed commented on Jun 2, 2022

    @DataGreed
    Author

    @srajiang could you please specify what kind of info do you need?

  4. DataGreed commented on Jun 2, 2022

    @DataGreed
    Author

    Can it probably be fixed with changing the implementation of DjangoInstallationStore.find_installation?

  5. DataGreed commented on Jun 2, 2022

    @DataGreed
    Author

    I've come up with a temporary fix.
    I've modified DjangoInstallationStore.find_installation and made it user_id-agnostic.
    Basically I've reduced this part:

        if user_id is None:
              rows = (
                  SlackInstallation.objects.filter(client_id=self.client_id)
                  .filter(enterprise_id=e_id)
                  .filter(team_id=t_id)
                  .order_by(F("installed_at").desc())[:1]
              )
          else:
              rows = (
                  SlackInstallation.objects.filter(client_id=self.client_id)
                  .filter(enterprise_id=e_id)
                  .filter(team_id=t_id)
                  .filter(user_id=user_id)
                  .order_by(F("installed_at").desc())[:1]
              )

    to this (without the same user_id, just the recent one):

                rows = (
                    SlackInstallation.objects.filter(client_id=self.client_id)
                    .filter(enterprise_id=e_id)
                    .filter(team_id=t_id)
                    .order_by(F("installed_at").desc())[:1]
                )

    I am not sure if it will cause any problems, though – it seems like it could if the token won't have the same permission scope as expected.

    Is there any way to fix this properly?

  6. seratch commented on Jun 2, 2022

    @seratch
    Contributor

    @DataGreed Thanks for letting us know the details of the issue that you encountered. And I agree that this is a bug on the installation store implementation in the Django example app. Also, the same potential issue can arise even with all the built-in installation store available here.

    Changing the query to be user_id agnostic does not work for the use cases where the OAuth flow has both bot scopes and user scopes. Thus, I am thinking of the following changes:

    • Run two queries with the installation database table
      • 1 - run a query without user_id to fetch the latest bot token data
      • 2 - run a query with user_id to fetch the latest user token for the user (if exists)
        Merging these query results into one unified Installation data should resolve the issue described here.

    Also, as an improvement that you can immediately do, I would suggest subscribing token revocation / uninstallation events and enabling built-in event listeners to properly maintain installation data. Please refer to my comment at #663 (comment) for more details.

    Thanks again for sharing this. I will work on the fix later today (in my local time Japan Standard Time +09:00)

  7. added
    bugSomething isn't working
    area:examplesissues related to example or sample code
    and removed
    questionFurther information is requested
    on Jun 2, 2022
  8. added this to the 1.14.1 milestone on Jun 2, 2022
  9. self-assigned this
    on Jun 2, 2022
  10. DataGreed commented on Jun 2, 2022

    @DataGreed
    Author

    @seratch first of all, thank you so much for a quick response and proposed fix, I really appreciate this.

    Changing the query to be user_id agnostic does not work for the use cases where the OAuth flow has both bot scopes and user scopes. Thus, I am thinking of the following changes:

    • Run two queries with the installation database table

      • 1 - run a query without user_id to fetch the latest bot token data
      • 2 - run a query with user_id to fetch the latest user token for the user (if exists)
        Merging these query results into one unified Installation data should resolve the issue described here.

    @seratch could you please suggest the best way to merge them to avoid any potential problems? Should I get user_id, user_token and user_scopes from the Installation found by user_id and get all other fields from the most recent Installation?

    Something like this:

    return Installation(
                  app_id=recent_installation.app_id,
                  enterprise_id=recent_installation.enterprise_id,
                  team_id=recent_installation.team_id,
                  bot_token=recent_installation.bot_token,
                  bot_id=recent_installation.bot_id,
                  bot_user_id=recent_installation.bot_user_id,
                  bot_scopes=recent_installation.bot_scopes,
    
                  # user-specific fields
                  user_id=user_installation.user_id if user_installation else recent_installation.user_id,
                  user_token=user_installation.user_id.user_token  if user_installation else recent_installation.user_token,
                  user_scopes=user_installation.user_scopes if user_installation else recent_installation.user_scopes,
    
                  incoming_webhook_url=recent_installation.incoming_webhook_url,
                  incoming_webhook_channel_id=recent_installation.incoming_webhook_channel_id,
                  incoming_webhook_configuration_url=recent_installation.incoming_webhook_configuration_url,
                  installed_at=recent_installation.installed_at.timestamp(),
              )

    ?

    Also, as an improvement that you can immediately do, I would suggest subscribing token revocation / uninstallation events and enabling built-in event listeners to properly maintain installation data. Please refer to my comment at #663 (comment) for more details.

    Thanks again for sharing this. I will work on the fix later today (in my local time Japan Standard Time +09:00)

    Thanks! I somehow overlooked this, I will definitely subscribe to these events.

  11. seratch commented on Jun 2, 2022

    @seratch
    Contributor

    Hi @DataGreed, here is a fix for this issue: #665 Could you verify if it works for your use case?

  12. DataGreed commented on Jun 2, 2022

    @DataGreed
    Author

    @seratch sorry for the late answer, I am in UTC+1 and had a sleepless night 😅 Yes, it does fix the problem, thank you!

  13. added a commit that references this issue on Jun 2, 2022
    854208f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions