Module slack_sdk.oauth
Modules for implementing the Slack OAuth flow
Sub-modules
slack_sdk.oauth.authorize_url_generator
slack_sdk.oauth.installation_store
slack_sdk.oauth.redirect_uri_page_renderer
slack_sdk.oauth.state_store
-
OAuth state parameter data store …
slack_sdk.oauth.state_utils
slack_sdk.oauth.token_rotation
Classes
class AuthorizeUrlGenerator (*, client_id: str, redirect_uri: Optional[str] = None, scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, authorization_url: str = 'https://slack.com/oauth/v2/authorize')
-
Expand source code
class AuthorizeUrlGenerator: def __init__( self, *, client_id: str, redirect_uri: Optional[str] = None, scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, authorization_url: str = "https://slack.com/oauth/v2/authorize", ): self.client_id = client_id self.redirect_uri = redirect_uri self.scopes = scopes self.user_scopes = user_scopes self.authorization_url = authorization_url def generate(self, state: str, team: Optional[str] = None) -> str: scopes = ",".join(self.scopes) if self.scopes else "" user_scopes = ",".join(self.user_scopes) if self.user_scopes else "" url = ( f"{self.authorization_url}?" f"state={state}&" f"client_id={self.client_id}&" f"scope={scopes}&" f"user_scope={user_scopes}" ) if self.redirect_uri is not None: url += f"&redirect_uri={self.redirect_uri}" if team is not None: url += f"&team={team}" return url
Methods
def generate(self, state: str, team: Optional[str] = None) ‑> str
class InstallationStore
-
The installation store interface.
The minimum required methods are:
- save(installation)
- find_installation(enterprise_id, team_id, user_id, is_enterprise_install)
If you would like to properly handle app uninstallations and token revocations, the following methods should be implemented.
- delete_installation(enterprise_id, team_id, user_id)
- delete_all(enterprise_id, team_id)
If your app needs only bot scope installations, the simpler way to implement would be:
- save(installation)
- find_bot(enterprise_id, team_id, is_enterprise_install)
- delete_bot(enterprise_id, team_id)
- delete_all(enterprise_id, team_id)
Expand source code
class InstallationStore: """The installation store interface. The minimum required methods are: * save(installation) * find_installation(enterprise_id, team_id, user_id, is_enterprise_install) If you would like to properly handle app uninstallations and token revocations, the following methods should be implemented. * delete_installation(enterprise_id, team_id, user_id) * delete_all(enterprise_id, team_id) If your app needs only bot scope installations, the simpler way to implement would be: * save(installation) * find_bot(enterprise_id, team_id, is_enterprise_install) * delete_bot(enterprise_id, team_id) * delete_all(enterprise_id, team_id) """ @property def logger(self) -> Logger: raise NotImplementedError() def save(self, installation: Installation): """Saves an installation data""" raise NotImplementedError() def save_bot(self, bot: Bot): """Saves a bot installation data""" raise NotImplementedError() def find_bot( self, *, enterprise_id: Optional[str], team_id: Optional[str], is_enterprise_install: Optional[bool] = False, ) -> Optional[Bot]: """Finds a bot scope installation per workspace / org""" raise NotImplementedError() def find_installation( self, *, enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None, is_enterprise_install: Optional[bool] = False, ) -> Optional[Installation]: """Finds a relevant installation for the given IDs. If the user_id is absent, this method may return the latest installation in the workspace / org. """ raise NotImplementedError() def delete_bot( self, *, enterprise_id: Optional[str], team_id: Optional[str], ) -> None: """Deletes a bot scope installation per workspace / org""" raise NotImplementedError() def delete_installation( self, *, enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None, ) -> None: """Deletes an installation that matches the given IDs""" raise NotImplementedError() def delete_all( self, *, enterprise_id: Optional[str], team_id: Optional[str], ): """Deletes all installation data for the given workspace / org""" self.delete_bot(enterprise_id=enterprise_id, team_id=team_id) self.delete_installation(enterprise_id=enterprise_id, team_id=team_id)
Subclasses
- AmazonS3InstallationStore
- CacheableInstallationStore
- FileInstallationStore
- SQLAlchemyInstallationStore
- SQLite3InstallationStore
Instance variables
prop logger : logging.Logger
-
Expand source code
@property def logger(self) -> Logger: raise NotImplementedError()
Methods
def delete_all(self, *, enterprise_id: Optional[str], team_id: Optional[str])
-
Deletes all installation data for the given workspace / org
def delete_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str]) ‑> None
-
Deletes a bot scope installation per workspace / org
def delete_installation(self, *, enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None) ‑> None
-
Deletes an installation that matches the given IDs
def find_bot(self, *, enterprise_id: Optional[str], team_id: Optional[str], is_enterprise_install: Optional[bool] = False) ‑> Optional[Bot]
-
Finds a bot scope installation per workspace / org
def find_installation(self, *, enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None, is_enterprise_install: Optional[bool] = False) ‑> Optional[Installation]
-
Finds a relevant installation for the given IDs. If the user_id is absent, this method may return the latest installation in the workspace / org.
def save(self, installation: Installation)
-
Saves an installation data
def save_bot(self, bot: Bot)
-
Saves a bot installation data
class OAuthStateStore
-
Expand source code
class OAuthStateStore: @property def logger(self) -> Logger: raise NotImplementedError() def issue(self, *args, **kwargs) -> str: raise NotImplementedError() def consume(self, state: str) -> bool: raise NotImplementedError()
Subclasses
Instance variables
prop logger : logging.Logger
-
Expand source code
@property def logger(self) -> Logger: raise NotImplementedError()
Methods
def consume(self, state: str) ‑> bool
def issue(self, *args, **kwargs) ‑> str
class OAuthStateUtils (*, cookie_name: str = 'slack-app-oauth-state', expiration_seconds: int = 600)
-
Expand source code
class OAuthStateUtils: cookie_name: str expiration_seconds: int default_cookie_name: str = "slack-app-oauth-state" default_expiration_seconds: int = 60 * 10 # 10 minutes def __init__( self, *, cookie_name: str = default_cookie_name, expiration_seconds: int = default_expiration_seconds, ): self.cookie_name = cookie_name self.expiration_seconds = expiration_seconds def build_set_cookie_for_new_state(self, state: str) -> str: return f"{self.cookie_name}={state}; " "Secure; " "HttpOnly; " "Path=/; " f"Max-Age={self.expiration_seconds}" def build_set_cookie_for_deletion(self) -> str: return f"{self.cookie_name}=deleted; " "Secure; " "HttpOnly; " "Path=/; " "Expires=Thu, 01 Jan 1970 00:00:00 GMT" def is_valid_browser( self, state: Optional[str], request_headers: Dict[str, Union[str, Sequence[str]]], ) -> bool: if state is None or request_headers is None or request_headers.get("cookie", None) is None: return False cookies = request_headers["cookie"] if isinstance(cookies, str): cookies = [cookies] for cookie in cookies: values = cookie.split(";") for value in values: if value.strip() == f"{self.cookie_name}={state}": return True return False
Class variables
var default_expiration_seconds : int
var expiration_seconds : int
Methods
def is_valid_browser(self, state: Optional[str], request_headers: Dict[str, Union[str, Sequence[str]]]) ‑> bool
class OpenIDConnectAuthorizeUrlGenerator (*, client_id: str, redirect_uri: str, scopes: Optional[Sequence[str]] = None, authorization_url: str = 'https://slack.com/openid/connect/authorize')
-
Expand source code
class OpenIDConnectAuthorizeUrlGenerator: """Refer to https://openid.net/specs/openid-connect-core-1_0.html""" def __init__( self, *, client_id: str, redirect_uri: str, scopes: Optional[Sequence[str]] = None, authorization_url: str = "https://slack.com/openid/connect/authorize", ): self.client_id = client_id self.redirect_uri = redirect_uri self.scopes = scopes self.authorization_url = authorization_url def generate(self, state: str, nonce: Optional[str] = None, team: Optional[str] = None) -> str: scopes = ",".join(self.scopes) if self.scopes else "" url = ( f"{self.authorization_url}?" "response_type=code&" f"state={state}&" f"client_id={self.client_id}&" f"scope={scopes}&" f"redirect_uri={self.redirect_uri}" ) if team is not None: url += f"&team={team}" if nonce is not None: url += f"&nonce={nonce}" return url
Methods
def generate(self, state: str, nonce: Optional[str] = None, team: Optional[str] = None) ‑> str
class RedirectUriPageRenderer (*, install_path: str, redirect_uri_path: str, success_url: Optional[str] = None, failure_url: Optional[str] = None)
-
Expand source code
class RedirectUriPageRenderer: def __init__( self, *, install_path: str, redirect_uri_path: str, success_url: Optional[str] = None, failure_url: Optional[str] = None, ): self.install_path = install_path self.redirect_uri_path = redirect_uri_path self.success_url = success_url self.failure_url = failure_url def render_success_page( self, app_id: str, team_id: Optional[str], is_enterprise_install: Optional[bool] = None, enterprise_url: Optional[str] = None, ) -> str: url = self.success_url if url is None: if is_enterprise_install is True and enterprise_url is not None and app_id is not None: url = f"{enterprise_url}manage/organization/apps/profile/{app_id}/workspaces/add" elif team_id is None or app_id is None: url = "slack://open" else: url = f"slack://app?team={team_id}&id={app_id}" browser_url = f"https://app.slack.com/client/{team_id}" return f""" <html> <head> <meta http-equiv="refresh" content="0; URL={html.escape(url)}"> <style> body {{ padding: 10px 15px; font-family: verdana; text-align: center; }} </style> </head> <body> <h2>Thank you!</h2> <p>Redirecting to the Slack App... click <a href="{html.escape(url)}">here</a>. If you use the browser version of Slack, click <a href="{html.escape(browser_url)}" target="_blank">this link</a> instead.</p> </body> </html> """ # noqa: E501 def render_failure_page(self, reason: str) -> str: return f""" <html> <head> <style> body {{ padding: 10px 15px; font-family: verdana; text-align: center; }} </style> </head> <body> <h2>Oops, Something Went Wrong!</h2> <p>Please try again from <a href="{html.escape(self.install_path)}">here</a> or contact the app owner (reason: {html.escape(reason)})</p> </body> </html> """ # noqa: E501
Methods
def render_failure_page(self, reason: str) ‑> str
def render_success_page(self, app_id: str, team_id: Optional[str], is_enterprise_install: Optional[bool] = None, enterprise_url: Optional[str] = None) ‑> str