Skip to content

Allow disabling of token auth test #158

Description

@mew1033

I've written a small app that is running in AWS lambda. I'm using the aws_lambda adapter and it is working great. However, I ran into a problem when attempting to write a test for an unrelated function in my lambda code (using pytest).

I'm setting up the App object in the global scope so that it's available for future function runs that reuse this container. This is in accordance with lambda's best practices. The problem is that I can't import my function code into my test at all unless I have a valid token, which I don't want/need to supply during testing. Here's an example:

import json
import os

import boto3
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler

# Grab secrets for the application.
# First check for local env secrets, otherwise get them from secrets manager
if os.environ.get("AWS_SAM_LOCAL") == "true":
    secrets = {
        "SLACK_SIGNING_SECRET": os.environ.get("OVERRIDE_BOT_TOKEN"),
        "SLACK_BOT_TOKEN": os.environ.get("OVERRIDE_BOT_TOKEN"),
    }
elif os.environ.get("SECRETS_MANAGER_SLACK_SECRET_NAME"):
    session = boto3.session.Session()
    client = session.client(service_name="secretsmanager")
    get_secret_value_response = client.get_secret_value(
        SecretId=os.environ["SECRETS_MANAGER_SLACK_SECRET_NAME"]
    )
    secrets = json.loads(get_secret_value_response["SecretString"])


app = App(
    process_before_response=True,
    signing_secret=secrets["SLACK_SIGNING_SECRET"],
    token=secrets["SLACK_BOT_TOKEN"],
)


@app.command("/hello-bolt-python-lambda")
def respond_to_slack_within_3_seconds(ack):
    ack("Thanks!")


def lambda_handler(event, context):
    slack_handler = SlackRequestHandler(app=app)
    return slack_handler.handle(event, context)

Trying to import that file in a test fails because the app can't verify the token. Here's the error:

Traceback (most recent call last):
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_bolt\app\app.py", line 225, in _init_middleware_list
    auth_test_result = self._client.auth_test(token=self._token)
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_sdk\web\client.py", line 756, in auth_test
    return self.api_call("auth.test", json=kwargs)
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_sdk\web\base_client.py", line 121, in api_call
    return self._sync_send(api_url=api_url, req_args=req_args)
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_sdk\web\base_client.py", line 164, in _sync_send
    additional_headers=headers,
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_sdk\web\base_client.py", line 292, in _urllib_api_call
    status_code=response["status"],
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_sdk\web\slack_response.py", line 201, in validate
    raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed.
The server responded with: {'ok': False, 'error': 'invalid_auth'}

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_bolt\app\app.py", line 212, in __init__
    self._init_middleware_list()
  File "C:\Users\User\folder\.venv\lib\site-packages\slack_bolt\app\app.py", line 230, in _init_middleware_list
    raise BoltError(error_auth_test_failure(err.response))
slack_bolt.error.BoltError: `token` is invalid (auth.test result: {'ok': False, 'error': 'invalid_auth'})

This can be easily reproduced by running: app = App(signing_secret="nope", token="no_token") and observing the same error as above.

I'd like to be able to temporarily disable the token auth test so I can import my code and run a test. I'm not testing any part of slack_bolt, but I've kept all my functions in a single file for simplicity's sake, and I'd like to test one of my other functions.

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Activity

  1. seratch commented on Nov 24, 2020

    @seratch
    Contributor

    @mew1033 Thanks for writing in and sharing the details! Your detailed description helped me understand what the issue you have is.

    I know it's pretty easy to add a new option to turn calling auth.test API off at the timing of initialization. But I would like to hold off adding such options because doing so a lot makes udnerstanding the initialization of App harder for developers.

    So, our recommendation for this use case is to use authorize function with the toekn. Bolt does this under the hood for you. You can learn more about this here: https://slack.dev/bolt-python/concepts#authorization

    def authorize(enterprise_id, team_id, user_id, client: WebClient, logger):
        # If you want to have a cache for this call, you can do so
        token = secrets["SLACK_BOT_TOKEN"]
        return AuthorizeResult.from_auth_test_response(
            auth_test_response=client.auth_test(token=token),
            bot_token=token
        )
    
    
    app = App(
        process_before_response=True,
        signing_secret=secrets["SLACK_SIGNING_SECRET"],
        authorize=authorize
    )

    Hope this was helpful to you.

  2. mew1033 commented on Nov 24, 2020

    @mew1033
    Author

    @seratch I tested your solution and it appears to work fine, thank you.
    However, it also seems overly complex and very much against the "Zen" of python.

    I also don't agree with the assumption that adding an option to the App initialization makes it harder for developers. If the option is available, and the default is kept the same as it is now, I don't think that makes using App() more complex.
    I suppose I can see that adding more options, and thus making the App() constructor slightly more complex could make reading it more difficult. But that is a (IMO) small price to pay for added functionality.

    Regardless, thank you for the code snippet that fixed my problem. I appreciate it!

  3. mew1033 commented on Nov 24, 2020

    @mew1033
    Author

    @seratch Another argument against a required auth test: The slack_sdk itself doesn't require it. You can create a WebClient instance with a bogus token no problem. It has the functionality built in to do an auth test, but it doesn't do it until explicitly asked.

  4. seratch commented on Nov 25, 2020

    @seratch
    Contributor

    @mew1033
    The reason why Bolt performs auth.test with its default settings is to construct AuthorizeResult that is used for the framework's functionalities. But, if your app can give sufficient information for AuthorizeResult without calling auth.test API (or storing the result somewhere accessible from your app in advance) to Bolt, calling the API in the app is not required at all.

    Thanks for your comments and feedback. I appreciate it. We may revisit the idea to add an option to skip the initialization but we're not going to take action at this point. Can we close this issue now?

  5. mew1033 commented on Nov 25, 2020

    @mew1033
    Author

    Thanks for the quick replies. I think that this can be closed now, yes.

  6. seratch commented on Dec 18, 2020

    @seratch
    Contributor

    We revisited this feature and decide to provide an option token_verification_enabled: bool = True to turn off the verification in constructor. I will come up with a pull request later.

  7. mew1033 commented on Dec 18, 2020

    @mew1033
    Author

    Awesome! Thank you!

  8. added this to the 1.1.4 milestone on Dec 18, 2020
  9. added a commit that references this issue on Dec 18, 2020
    edd9279
  10. seratch commented on Dec 18, 2020

    @seratch
    Contributor

    I am going to release a new patch version soon (the last release in 2020):
    https://github.com/slackapi/bolt-python/milestone/18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions