slackapi / bolt-python

A framework to build Slack apps using Python
https://tools.slack.dev/bolt-python/
MIT License
1.06k stars 245 forks source link

Unable to get user-token in context.client.token for handler function. #871

Closed srbcheema1 closed 1 year ago

srbcheema1 commented 1 year ago

https://github.com/slackapi/bolt-python/blob/8334d17ddac15b6e41cc15a6cce533745c20ba7d/slack_bolt/middleware/authorization/multi_teams_authorization.py#L83-L87

In these lines, bot token always take precedence over user token.

The following code gives me error "not_allowed_token_type" when I try to set profile of an authorized user.

import os
from slack_bolt import App

from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore

oauth_settings = OAuthSettings(
    client_id=os.environ.get("SLACK_CLIENT_ID"),
    client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
    scopes=["chat:write", "chat:write.public", "commands", "channels:history"],
    user_scopes=["users.profile:write", "users.profile:read", "users:read"],
    installation_store=FileInstallationStore(
        base_dir="./.oauth-data/installations"
    ),
    state_validation_enabled=False,
    install_page_rendering_enabled=False,
)

app = App(
    signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
    oauth_settings=oauth_settings,
)

@app.message("hello")
def update_status(message, client):
    """
    Want client.token to be user token, but getting bot token.
    """
    if "xoxb-" in client.token:
        print("bot token found")
    else:
        print("user token found")

    client.users_profile_set(
        user=message["user"],
        profile={
            "status_text": "riding the train home",
            "status_emoji": ":mountain_railway:",
            "status_expiration": 1532627506,
        },
    )

app.start(port=3000)

So when this function is triggered by a user by typing hello in a group, it provides client with bot token, while I want to use user-token to update his profile.

Error I am getting is:

bot token found
127.0.0.1 - - [24/Mar/2023 01:39:59] "POST /slack/events HTTP/1.1" 200 -
Failed to run listener function (error: The request to the Slack API failed. (url: https://www.slack.com/api/users.profile.set)
The server responded with: {'ok': False, 'error': 'not_allowed_token_type'})
Traceback (most recent call last):
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_bolt/listener/thread_runner.py", line 120, in run_ack_function_asynchronously
    listener.run_ack_function(request=request, response=response)
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_bolt/listener/custom_listener.py", line 56, in run_ack_function
    this_func=self.ack_function,
  File "srb_test.py", line 58, in update_status
    "status_expiration": 1532627506,
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_sdk/web/client.py", line 4478, in users_profile_set
    return self.api_call("users.profile.set", json=kwargs)
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_sdk/web/base_client.py", line 156, in api_call
    return self._sync_send(api_url=api_url, req_args=req_args)
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_sdk/web/base_client.py", line 194, in _sync_send
    additional_headers=headers,
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_sdk/web/base_client.py", line 316, in _urllib_api_call
    status_code=response["status"],
  File "/home/srb/.venv/lib64/python3.7/site-packages/slack_sdk/web/slack_response.py", line 199, in validate
    raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/users.profile.set)
The server responded with: {'ok': False, 'error': 'not_allowed_token_type'}

For now I am using this hack:

temp_authorize = oauth_settings.authorize

def authorize_wrapper(context, enterprise_id, team_id, user_id):
    resp = temp_authorize(
        context=context,
        enterprise_id=enterprise_id,
        team_id=team_id,
        user_id=user_id,
    )
    resp.bot_token = None
    return resp

oauth_settings.authorize = authorize_wrapper

This hack works for me now but I would like to know what should be the correct way of getting user-token in context.client.token in a handler function. OR How to use both bot-token and user-token in a handler-functions of slack-bolt app using oauth_settings ?

Versions:

slack-bolt==1.16.4
slack-sdk==3.20.2
seratch commented 1 year ago

Hi @srbcheema1, thanks for asking the question. You don't need to have such a workaround. When your installation_store manages user tokens too and the user associated with the request has installed the app with user scopes, his/her user token is available in context object (under context.autorize_result too). Thus, your listener can be like this:

@app.message("hello")
def update_status(message, client, context: BoltContext):
    client.users_profile_set(
        token=context.user_token,  # override the token only for this method call
        user=message["user"],
        profile={
            "status_text": "riding the train home",
            "status_emoji": ":mountain_railway:",
            "status_expiration": 1532627506,
        },
    )

I hope this helps.

srbcheema1 commented 1 year ago

Thanks for the help @seratch It is not pretty straight forward to figure this out as the function client.user_profile_set doesn't take "token" as a direct parameter. Is there any documentation that I can refer for this ?

seratch commented 1 year ago

@srbcheema1 I hear you on that. Every single Slack Web API has its requirements on the necessary scopes. So, to learn such, you can check API doc’s “required scopes” section: https://api.slack.com/methods/users.profile.set

If everything is clear now, would you mind if we close this issue?

srbcheema1 commented 1 year ago

@srbcheema1 I hear you on that. Every single Slack Web API has its requirements on the necessary scopes.

I am familiar with this.

It is not pretty straight forward to figure this out as the function client.user_profile_set doesn't take "token" as a direct parameter. Is there any documentation that I can refer for this ?

Rephrasing my question: How to know that client.user_profile_set function can take token as input? Any documentation you can quote that can tell me token can be passed to this function.

seratch commented 1 year ago

@srbcheema1 As you may already notice, context.token and the token that is embodied in client is primarily a bot token. So, as long as your app requests a bot token (and this is the expected setting for most apps), the token is always a bot token. This means that the token is not useable for the Web APIs that accept only a user token.

Please note that most of the common operations you can do with Slack's Web APIs can be done with a bot token but the specific API you wanted to use this time accepts only a user token. The logic to pick the primary token up may need to be clearly mentioned somewhere in the advanced topic sections. We will consider the improvement.

Do you have anything else to confirm here?

srbcheema1 commented 1 year ago

Thanks for the response @seratch

The logic to pick the primary token up may need to be clearly mentioned somewhere in the advanced topic sections.

Yeah this would help developers understand the logic.

Closing here, nothing pending from my end.