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

Google Cloud Storage OAuth backend #1628

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

ccaruceru
Copy link

Summary

Add Google Cloud Storage (GCS) support for OAuth installation and state stores, as a follow up from slackapi/bolt-python#962 (comment).
(ported from ccaruceru/slack-multireact/multi_reaction_add/oauth)

Testing

  • create/reuse a slack app and install it in a workspace
  • use Bolt to write a small app to ask the user for some permissions on app installation, in conjunction with the newly added GCS storage
    e.g.
from google.cloud.storage import Client
from slack_bolt import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.state_store.google_cloud_storage import GoogleCloudStorageOAuthStateStore
from slack_sdk.oauth.installation_store.google_cloud_storage import GoogleCloudStorageInstallationStore

bucket_name = "GOOGLE_BUCKET_NAME"
slack_client_id = "SLACK_CLIENT_ID"
slack_client_secret = "SLACK_CLIENT_SECRET"
slack_signing_secret = "SLACK_SIGNING_SECRET"

storage_client = Client()

app = App(
    signing_secret=slack_signing_secret,
    oauth_settings=OAuthSettings(
        install_page_rendering_enabled=False,
        client_id=slack_client_id,
        client_secret=slack_client_secret,
        scopes=[],  # scopes for bot operations
        user_scopes=["reactions:write"], # scopes for operations on behalf of user
        installation_store=GoogleCloudStorageInstallationStore(
            storage_client=storage_client,
            bucket_name=bucket_name,
            client_id=slack_client_id
        ),
        state_store=GoogleCloudStorageOAuthStateStore(
            storage_client=storage_client,
            bucket_name=bucket_name,
            expiration_seconds=600
        )
    )
)

@app.event("message")
def handle_message_events(event, client, context):
    """Listens for messages containing 'thumbs up' and reacts with a :thumbsup: emoji"""
    text = event.get("text", "").lower()
    channel = event.get("channel")
    timestamp = event.get("ts")
    if "thumbs up" in text:
        client.token = context.user_token
        client.reactions_add(
            channel=channel,
            name="thumbsup",
            timestamp=timestamp,
        )
        print("Added :thumbsup: reaction!")
  • go to app's /slack/install page
    • observe a temporary oauth token is issued in the bucket
  • finish the install flow
    • observe that the temporary token is gone
    • observe that bot* and installer* files are created
  • interact with the newly added app (e.g. for the above app: add/invite the app to a public channel, type a message with "thumbs up" in it and see if the 👍 emoji is added to the message)
    • check that there are no errors in the app logs
    • check that the bot and installer files are unchanged

Category

  • slack_sdk.web.WebClient (sync/async) (Web API client)
  • slack_sdk.webhook.WebhookClient (sync/async) (Incoming Webhook, response_url sender)
  • slack_sdk.socket_mode (Socket Mode client)
  • slack_sdk.signature (Request Signature Verifier)
  • slack_sdk.oauth (OAuth Flow Utilities)
  • slack_sdk.models (UI component builders)
  • slack_sdk.scim (SCIM API client)
  • slack_sdk.audit_logs (Audit Logs API client)
  • slack_sdk.rtm_v2 (RTM client)
  • /docs (Documents)
  • /tutorial (PythOnBoardingBot tutorial)
  • tests/integration_tests (Automated tests for this library)

Requirements

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh after making the changes.

@ccaruceru
Copy link
Author

Since the time I wrote the original code, I saw there are a couple additions to the other installation stores (like the S3 one) compared to what I did, like historical data and more situations to handle when finding and deleting installs. Let me know what are your thoughts on the proposed changes and if we should tackle those too in this PR 🙂

@seratch seratch added this to the 3.x milestone Jan 6, 2025
@seratch seratch marked this pull request as draft January 6, 2025 09:27
@seratch
Copy link
Member

seratch commented Jan 6, 2025

Hi @ccaruceru, thanks for sending this pull request! As you can see here, all the existing built-in installation store implementations pass the same set of scenario tests. If we accept a new one, it must pass the same tests. I'm not sure if you have time or are willing to spend more time, but adding complete test assets to ensure the same quality would be appreciated.

@ccaruceru
Copy link
Author

hi @seratch! I'll have a look at the tests this week and see if I can come up with something similar 👌🏻

@ccaruceru
Copy link
Author

@seratch I added the tests as you asked and modified the GoogleCloudStorageInstallationStore to meet the expectations of the new test cases. let me know what you're thinking about the implementation now.

Args:
installation (Installation): information about the user and the app usage authorization
"""
self.save(installation)
Copy link

@galuszkak galuszkak Jan 11, 2025

Choose a reason for hiding this comment

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

I could be wrong, but to make this really async you should use for async methods gcloud-aio-storage package that supports async GCS as there is no support for it in original Google Cloud Storage package:
https://talkiq.github.io/gcloud-aio/autoapi/storage/index.html

This is to all async methods here - but also question to other storages (File, S3, SQLite etc.) that are doing this and they aren't trully async as they just use synchronous methods. SQLite for example needs to use aiosqlite for async methods to be trully async.

Copy link
Author

Choose a reason for hiding this comment

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

Thank's for pointing this out. It's true that this implementation doesn't provide true async support because it relies on the synchronous methods of the official google-cloud-storage package. However, one of the goals here is to unblock users who need to work with the async version of the app and still support GCS.

Looking at gcloud-aio-storage, it does offer proper async methods but it lacks support for certain features like the max_results parameter when doing list_blobs that's used inside the InstallationStore.

I agree that the asynchronous aspect of the app as a whole should be addressed, but in a future PR to ensure a more consistent approach across all backend/storage providers.

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

Successfully merging this pull request may close these issues.

3 participants