Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
DataGreed opened this issue Jun 1, 2022 · 9 comments · Fixed by #665
Assignees
Labels
area:async area:examples issues related to example or sample code area:sync bug Something isn't working
Milestone

Comments

@DataGreed
Copy link

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
@DataGreed
Copy link
Author

DataGreed commented Jun 1, 2022

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)

@DataGreed
Copy link
Author

DataGreed commented Jun 1, 2022

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.

@srajiang srajiang added question Further information is requested need info labels Jun 1, 2022
@DataGreed
Copy link
Author

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

@DataGreed
Copy link
Author

DataGreed commented Jun 2, 2022

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

@DataGreed
Copy link
Author

DataGreed commented Jun 2, 2022

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?

@seratch
Copy link
Member

seratch commented Jun 2, 2022

@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)

@seratch seratch added bug Something isn't working area:async area:sync area:examples issues related to example or sample code and removed question Further information is requested need info labels Jun 2, 2022
@seratch seratch added this to the 1.14.1 milestone Jun 2, 2022
@seratch seratch self-assigned this Jun 2, 2022
@DataGreed
Copy link
Author

DataGreed commented Jun 2, 2022

@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.

@seratch
Copy link
Member

seratch commented Jun 2, 2022

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

@DataGreed
Copy link
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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:async area:examples issues related to example or sample code area:sync bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants