def save(self, installation: Installation): i = installation.to_dict() i["client_id"] = self.client_id SlackInstallation(**i).save() b = installation.to_bot().to_dict() b["client_id"] = self.client_id SlackBot(**b).save()
def save(self, installation: Installation): i = installation.to_dict() if is_naive(i['installed_at']): i['installed_at'] = make_aware(i['installed_at']) if 'bot_token_expires_at' in i and i[ 'bot_token_expires_at'] is not None and is_naive( i['bot_token_expires_at']): i['bot_token_expires_at'] = make_aware(i['bot_token_expires_at']) if 'user_token_expires_at' in i and i[ 'user_token_expires_at'] is not None and is_naive( i['user_token_expires_at']): i['user_token_expires_at'] = make_aware(i['user_token_expires_at']) i['client_id'] = self.client_id row_to_update = (SlackInstallation.objects.filter( client_id=self.client_id).filter( enterprise_id=installation.enterprise_id).filter( team_id=installation.team_id).filter( installed_at=i['installed_at']).first()) if row_to_update is not None: for key, value in i.items(): setattr(row_to_update, key, value) row_to_update.save() else: SlackInstallation(**i).save() self.save_bot(installation.to_bot())
def test_installation_custom_fields(self): installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222", ) self.assertIsNotNone(installation) installation.set_custom_value("service_user_id", "XYZ123") # the same names in custom_values are ignored installation.set_custom_value("app_id", "A222") self.assertEqual(installation.get_custom_value("service_user_id"), "XYZ123") self.assertEqual(installation.to_dict().get("service_user_id"), "XYZ123") self.assertEqual(installation.to_dict().get("app_id"), "A111") bot = installation.to_bot() self.assertEqual(bot.app_id, "A111") self.assertEqual(bot.get_custom_value("service_user_id"), "XYZ123") self.assertEqual(bot.to_dict().get("app_id"), "A111") self.assertEqual(bot.to_dict().get("service_user_id"), "XYZ123")
async def async_save(self, installation: Installation): async with Database(self.database_url) as database: async with database.transaction(): i = installation.to_dict() i["client_id"] = self.client_id await database.execute(self.installations.insert(), i) b = installation.to_bot().to_dict() b["client_id"] = self.client_id await database.execute(self.bots.insert(), b)
def save(self, installation: Installation): i = installation.to_dict() if is_naive(i["installed_at"]): i["installed_at"] = make_aware(i["installed_at"]) i["client_id"] = self.client_id SlackInstallation(**i).save() b = installation.to_bot().to_dict() if is_naive(b["installed_at"]): b["installed_at"] = make_aware(b["installed_at"]) b["client_id"] = self.client_id SlackBot(**b).save()
async def test_save_and_find_token_rotation(self): sqlite3_store = SQLite3InstallationStore( database="logs/cacheable.db", client_id="111.222" ) sqlite3_store.init() store = AsyncCacheableInstallationStore(sqlite3_store) installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-initial", bot_scopes=["chat:write"], bot_user_id="U222", bot_refresh_token="xoxe-1-initial", bot_token_expires_in=43200, ) await store.async_save(installation) bot = await store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) self.assertEqual(bot.bot_refresh_token, "xoxe-1-initial") installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-refreshed", bot_scopes=["chat:write"], bot_user_id="U222", bot_refresh_token="xoxe-1-refreshed", bot_token_expires_in=43200, ) await store.async_save(installation) bot = await store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) self.assertEqual(bot.bot_refresh_token, "xoxe-1-refreshed") os.remove("logs/cacheable.db") bot = await sqlite3_store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNone(bot) bot = await store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot)
def run_installation(self, code: str) -> Optional[Installation]: try: oauth_response: SlackResponse = self.client.oauth_v2_access( code=code, client_id=self.settings.client_id, client_secret=self.settings.client_secret, redirect_uri=self.settings.redirect_uri, # can be None ) installed_enterprise: Dict[str, str] = ( oauth_response.get("enterprise") or {} ) is_enterprise_install: bool = ( oauth_response.get("is_enterprise_install") or False ) installed_team: Dict[str, str] = oauth_response.get("team") or {} installer: Dict[str, str] = oauth_response.get("authed_user") or {} incoming_webhook: Dict[str, str] = ( oauth_response.get("incoming_webhook") or {} ) bot_token: Optional[str] = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id: Optional[str] = None enterprise_url: Optional[str] = None if bot_token is not None: auth_test = self.client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") return Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get("scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url", None ), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) except SlackApiError as e: message = ( f"Failed to fetch oauth.v2.access result with code: {code} - error: {e}" ) self.logger.warning(message) return None
def oauth_callback(): # Retrieve the auth code and state from the request params if "code" in request.args: state = request.args["state"] if state_store.consume(state): code = request.args["code"] client = WebClient() # no prepared token needed for this app oauth_response = client.oauth_v2_access( client_id=client_id, client_secret=client_secret, code=code) logger.info(f"oauth.v2.access response: {oauth_response}") installed_enterprise = oauth_response.get("enterprise", {}) is_enterprise_install = oauth_response.get("is_enterprise_install") installed_team = oauth_response.get("team", {}) installer = oauth_response.get("authed_user", {}) incoming_webhook = oauth_response.get("incoming_webhook", {}) bot_token = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id = None enterprise_url = None if bot_token is not None: auth_test = client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") installation = Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get( "scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url"), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) installation_store.save(installation) return "Thanks for installing this app!" else: return make_response( f"Try the installation again (the state value is already expired)", 400) error = request.args["error"] if "error" in request.args else "" return make_response( f"Something is wrong with the installation (error: {error})", 400)
async def test_save_and_find(self): sqlite3_store = SQLite3InstallationStore( database="logs/cacheable.db", client_id="111.222" ) sqlite3_store.init() store = AsyncCacheableInstallationStore(sqlite3_store) installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222", ) await store.async_save(installation) bot = await store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) os.remove("logs/cacheable.db") bot = await sqlite3_store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNone(bot) bot = await store.async_find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot)
async def oauth_callback(req: Request): # Retrieve the auth code and state from the request params if "code" in req.args: state = req.args.get("state") if state_store.consume(state): code = req.args.get("code") client = AsyncWebClient() # no prepared token needed for this app oauth_response = await client.oauth_v2_access( client_id=client_id, client_secret=client_secret, code=code) logger.info(f"oauth.v2.access response: {oauth_response}") installed_enterprise = oauth_response.get("enterprise") or {} installed_team = oauth_response.get("team") or {} installer = oauth_response.get("authed_user") or {} incoming_webhook = oauth_response.get("incoming_webhook") or {} bot_token = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id = None if bot_token is not None: auth_test = await client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] installation = Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), team_id=installed_team.get("id"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get( "scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url"), ) installation_store.save(installation) return HTTPResponse(status=200, body="Thanks for installing this app!") else: return HTTPResponse( status=400, body= "Try the installation again (the state value is already expired)" ) error = req.args["error"] if "error" in req.args else "" return HTTPResponse( status=400, body=f"Something is wrong with the installation (error: {error})")
def test_org_installation(self): store = SQLite3InstallationStore(database="logs/test.db", client_id="111.222") installation = Installation( app_id="AO111", enterprise_id="EO111", user_id="UO111", bot_id="BO111", bot_token="xoxb-O111", bot_scopes=["chat:write"], bot_user_id="UO222", is_enterprise_install=True, ) store.save(installation) bot = store.find_bot(enterprise_id="EO111", team_id=None) self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="EO111", team_id="TO222", is_enterprise_install=True) self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="EO111", team_id="TO222") self.assertIsNone(bot) bot = store.find_bot(enterprise_id=None, team_id="TO111") self.assertIsNone(bot) i = store.find_installation(enterprise_id="EO111", team_id=None) self.assertIsNotNone(i) i = store.find_installation(enterprise_id="EO111", team_id="T111", is_enterprise_install=True) self.assertIsNotNone(i) i = store.find_installation(enterprise_id="EO111", team_id="T222") self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(i) i = store.find_installation(enterprise_id="EO111", team_id=None, user_id="UO111") self.assertIsNotNone(i) i = store.find_installation( enterprise_id="E111", team_id="T111", is_enterprise_install=True, user_id="U222", ) self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T222", user_id="U111") self.assertIsNone(i)
def test_org_installation(self): installation = Installation( app_id="AO111", enterprise_id="EO111", user_id="UO111", bot_id="BO111", bot_token="xoxb-O111", bot_scopes=["chat:write"], bot_user_id="UO222", is_enterprise_install=True, ) self.store.save(installation) bot = self.store.find_bot(enterprise_id="EO111", team_id=None) self.assertIsNotNone(bot) bot = self.store.find_bot(enterprise_id="EO111", team_id="TO222", is_enterprise_install=True) self.assertIsNotNone(bot) bot = self.store.find_bot(enterprise_id="EO111", team_id="TO222") self.assertIsNone(bot) bot = self.store.find_bot(enterprise_id=None, team_id="TO111") self.assertIsNone(bot) i = self.store.find_installation(enterprise_id="EO111", team_id=None) self.assertIsNotNone(i) i = self.store.find_installation(enterprise_id="EO111", team_id="T111", is_enterprise_install=True) self.assertIsNotNone(i) i = self.store.find_installation(enterprise_id="EO111", team_id="T222") self.assertIsNone(i) i = self.store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(i) i = self.store.find_installation(enterprise_id="EO111", team_id=None, user_id="UO111") self.assertIsNotNone(i) i = self.store.find_installation( enterprise_id="E111", team_id="T111", is_enterprise_install=True, user_id="U222", ) self.assertIsNone(i) i = self.store.find_installation(enterprise_id=None, team_id="T222", user_id="U111") self.assertIsNone(i)
async def run_installation(self, code: str) -> Optional[Installation]: try: oauth_response: AsyncSlackResponse = await self.client.oauth_v2_access( code=code, client_id=self.client_id, client_secret=self.client_secret, redirect_uri=self.redirect_uri, # can be None ) installed_enterprise: Dict[str, str] = oauth_response.get( "enterprise", {}) installed_team: Dict[str, str] = oauth_response.get("team", {}) installer: Dict[str, str] = oauth_response.get("authed_user", {}) incoming_webhook: Dict[str, str] = oauth_response.get( "incoming_webhook", {}) bot_token: Optional[str] = oauth_response.get("access_token", None) # NOTE: oauth.v2.access doesn't include bot_id in response bot_id: Optional[str] = None if bot_token is not None: auth_test = await self.client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] return Installation( app_id=oauth_response.get("app_id", None), enterprise_id=installed_enterprise.get("id", None), team_id=installed_team.get("id", None), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id", None), bot_scopes=oauth_response.get("scope", None), # comma-separated string user_id=installer.get("id", None), user_token=installer.get("access_token", None), user_scopes=installer.get("scope", None), # comma-separated string incoming_webhook_url=incoming_webhook.get("url", None), incoming_webhook_channel_id=incoming_webhook.get( "channel_id", None), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url", None), ) except SlackApiError as e: message = ( f"Failed to fetch oauth.v2.access result with code: {code} - error: {e}" ) self.logger.warning(message) return None
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]: e_id = enterprise_id or None t_id = team_id or None if is_enterprise_install: t_id = None 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]) if len(rows) > 0: i = rows[0] return Installation( app_id=i.app_id, enterprise_id=i.enterprise_id, team_id=i.team_id, bot_token=i.bot_token, bot_refresh_token=i.bot_refresh_token, bot_token_expires_at=i.bot_token_expires_at, bot_id=i.bot_id, bot_user_id=i.bot_user_id, bot_scopes=i.bot_scopes, user_id=i.user_id, user_token=i.user_token, user_refresh_token=i.user_refresh_token, user_token_expires_at=i.user_token_expires_at, user_scopes=i.user_scopes, incoming_webhook_url=i.incoming_webhook_url, incoming_webhook_channel_id=i.incoming_webhook_channel_id, incoming_webhook_configuration_url=i. incoming_webhook_configuration_url, installed_at=i.installed_at, ) return None
async def async_find_installation( self, *, enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None, is_enterprise_install: Optional[bool] = False ) -> Optional[Installation]: assert enterprise_id == "E111" assert team_id is None return Installation( enterprise_id="E111", team_id=None, user_id=user_id, bot_token=valid_token, bot_id="B111", )
def test_save_and_find(self): installation = Installation(app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222") self.store.save(installation) bot = self.store.find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) bot = self.store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) bot = self.store.find_bot(enterprise_id=None, team_id="T111") self.assertIsNone(bot)
async def test_token_rotation_disabled(self): installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxe.xoxp-1-initial", bot_scopes=["chat:write"], bot_user_id="U222", ) should_not_be_refreshed = await self.token_rotator.perform_token_rotation( installation=installation, minutes_before_expiration=60 * 24 * 365 ) self.assertIsNone(should_not_be_refreshed) should_not_be_refreshed = await self.token_rotator.perform_token_rotation( installation=installation, minutes_before_expiration=1 ) self.assertIsNone(should_not_be_refreshed)
def test_save_and_find(self): store = SQLite3InstallationStore(database="logs/test.db", client_id="111.222") installation = Installation(app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222") store.save(installation) bot = store.find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) bot = store.find_bot(enterprise_id=None, team_id="T111") self.assertIsNone(bot)
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]: return Installation( app_id="A111", enterprise_id="E111", team_id="T0G9PQBBK", bot_token="xoxb-valid-2", bot_id="B", bot_user_id="W", bot_scopes=["commands", "chat:write"], user_id="W11111", user_token="xoxp-valid", user_scopes=["search:read"], installed_at=datetime.datetime.now().timestamp(), )
def find_installation( self, *, enterprise_id: Optional[str] = None, team_id: Optional[str], user_id: Optional[str] = None, is_enterprise_install: Optional[bool] = False, ) -> Optional[Installation]: assert team_id try: teamiclink_bot = self.read_bot(team_id=team_id) except MissingBotError: return None return Installation( user_id="", team_id=teamiclink_bot.team_id, bot_token=teamiclink_bot.bot_token, bot_id=teamiclink_bot.bot_id, bot_user_id=teamiclink_bot.bot_user_id, installed_at=teamiclink_bot.installed_at.timestamp(), )
def perform_token_rotation( # type: ignore self, *, installation: Installation, minutes_before_expiration: int = 120, # 2 hours by default ) -> Optional[Installation]: """Performs token rotation if the underlying tokens (bot / user) are expired / expiring. Args: installation: the current installation data minutes_before_expiration: the minutes before the token expiration Returns: None if no rotation is necessary for now. """ # TODO: make the following two calls in parallel for better performance # bot rotated_bot: Optional[ Bot] = self.perform_bot_token_rotation( # type: ignore bot=installation.to_bot(), minutes_before_expiration=minutes_before_expiration, ) # user rotated_installation: Optional[ Installation] = self.perform_user_token_rotation( # type: ignore installation=installation, minutes_before_expiration=minutes_before_expiration, ) if rotated_bot is not None: if rotated_installation is None: rotated_installation = Installation( **installation.to_dict()) # type: ignore rotated_installation.bot_token = rotated_bot.bot_token rotated_installation.bot_refresh_token = rotated_bot.bot_refresh_token rotated_installation.bot_token_expires_at = rotated_bot.bot_token_expires_at return rotated_installation
async def test_refresh_error(self): token_rotator = AsyncTokenRotator( client=AsyncWebClient(base_url="http://localhost:8888", token=None), client_id="111.222", client_secret="invalid_value", ) installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxe.xoxp-1-initial", bot_scopes=["chat:write"], bot_user_id="U222", bot_refresh_token="xoxe-1-initial", bot_token_expires_in=43200, ) with self.assertRaises(SlackTokenRotationError): await token_rotator.perform_token_rotation( installation=installation, minutes_before_expiration=60 * 24 * 365 )
def perform_user_token_rotation( # type: ignore self, *, installation: Installation, minutes_before_expiration: int = 120, # 2 hours by default ) -> Optional[Installation]: """Performs user token rotation if the underlying user token is expired / expiring. Args: installation: the current installation data minutes_before_expiration: the minutes before the token expiration Returns: None if no rotation is necessary for now. """ if installation.user_token_expires_at is None: return None if installation.user_token_expires_at > time( ) + minutes_before_expiration * 60: return None try: refresh_response = self.client.oauth_v2_access( client_id=self.client_id, client_secret=self.client_secret, grant_type="refresh_token", refresh_token=installation.user_refresh_token, ) if refresh_response.get("token_type") != "user": return None refreshed_installation = Installation( **installation.to_dict()) # type: ignore refreshed_installation.user_token = refresh_response.get( "access_token") refreshed_installation.user_refresh_token = refresh_response.get( "refresh_token") refreshed_installation.user_token_expires_at = int(time()) + int( refresh_response.get("expires_in")) return refreshed_installation except SlackApiError as e: raise SlackTokenRotationError(e)
def test_installation(self): installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222", ) self.assertIsNotNone(installation) self.assertEqual(installation.app_id, "A111") self.assertIsNotNone(installation.to_bot()) self.assertIsNotNone(installation.to_bot().app_id, "A111") self.assertIsNotNone(installation.to_dict()) self.assertEqual(installation.to_dict().get("app_id"), "A111")
async def oauth_callback(req: Request): # Retrieve the auth code and state from the request params if "code" in req.args: state = req.args.get("state") if state_store.consume(state): code = req.args.get("code") client = AsyncWebClient() # no prepared token needed for this app oauth_response = await client.oauth_v2_access( client_id=client_id, client_secret=client_secret, code=code ) logger.info(f"oauth.v2.access response: {oauth_response}") installed_enterprise = oauth_response.get("enterprise") or {} installed_team = oauth_response.get("team") or {} installer = oauth_response.get("authed_user") or {} incoming_webhook = oauth_response.get("incoming_webhook") or {} bot_token = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id = None if bot_token is not None: auth_test = await client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] installation = Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), team_id=installed_team.get("id"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get("scope"), # comma-separated string bot_refresh_token=oauth_response.get("refresh_token"), bot_token_expires_in=oauth_response.get("expires_in"), user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string user_refresh_token=installer.get("refresh_token"), user_token_expires_in=installer.get("expires_in"), incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url" ), ) await installation_store.async_save(installation) html = redirect_page_renderer.render_success_page( app_id=installation.app_id, team_id=installation.team_id, is_enterprise_install=installation.is_enterprise_install, enterprise_url=installation.enterprise_url, ) return HTTPResponse( status=200, headers={ "Content-Type": "text/html; charset=utf-8", }, body=html, ) else: html = redirect_page_renderer.render_failure_page( "the state value is already expired" ) return HTTPResponse( status=400, headers={ "Content-Type": "text/html; charset=utf-8", }, body=html, ) error = req.args.get("error") if "error" in req.args else "" return HTTPResponse( status=400, body=f"Something is wrong with the installation (error: {error})" )
def test_save_and_find(self): installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxb-111", bot_scopes=["chat:write"], bot_user_id="U222", ) self.store.save(installation) store = self.store # find bots bot = store.find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) bot = store.find_bot(enterprise_id=None, team_id="T111") self.assertIsNone(bot) # delete bots store.delete_bot(enterprise_id="E111", team_id="T222") bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) # find installations i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNotNone(i) i = store.find_installation(enterprise_id="E111", team_id="T222") self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id="T111", user_id="U111") self.assertIsNotNone(i) i = store.find_installation(enterprise_id="E111", team_id="T111", user_id="U222") self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id="T222", user_id="U111") self.assertIsNone(i) # delete installations store.delete_installation(enterprise_id="E111", team_id="T111", user_id="U111") i = store.find_installation(enterprise_id="E111", team_id="T111", user_id="U111") self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNone(i) # delete all store.save(installation) store.delete_all(enterprise_id="E111", team_id="T111") i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id="T111", user_id="U111") self.assertIsNone(i) bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot)
def test_save_and_find_token_rotation(self): sqlite3_store = SQLite3InstallationStore( database="logs/cacheable.db", client_id="111.222" ) sqlite3_store.init() store = CacheableInstallationStore(sqlite3_store) installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxe.xoxp-1-initial", bot_scopes=["chat:write"], bot_user_id="U222", bot_refresh_token="xoxe-1-initial", bot_token_expires_in=43200, ) store.save(installation) bot = store.find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) self.assertEqual(bot.bot_refresh_token, "xoxe-1-initial") # Update the existing data refreshed_installation = Installation( app_id="A111", enterprise_id="E111", team_id="T111", user_id="U111", bot_id="B111", bot_token="xoxe.xoxp-1-refreshed", bot_scopes=["chat:write"], bot_user_id="U222", bot_refresh_token="xoxe-1-refreshed", bot_token_expires_in=43200, ) store.save(refreshed_installation) # find bots bot = store.find_bot(enterprise_id="E111", team_id="T111") self.assertIsNotNone(bot) self.assertEqual(bot.bot_refresh_token, "xoxe-1-refreshed") bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) bot = store.find_bot(enterprise_id=None, team_id="T111") self.assertIsNone(bot) # delete bots store.delete_bot(enterprise_id="E111", team_id="T222") bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot) # find installations i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNotNone(i) i = store.find_installation(enterprise_id="E111", team_id="T222") self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(i) i = store.find_installation( enterprise_id="E111", team_id="T111", user_id="U111" ) self.assertIsNotNone(i) i = store.find_installation( enterprise_id="E111", team_id="T111", user_id="U222" ) self.assertIsNone(i) i = store.find_installation( enterprise_id="E111", team_id="T222", user_id="U111" ) self.assertIsNone(i) # delete installations store.delete_installation(enterprise_id="E111", team_id="T111", user_id="U111") i = store.find_installation( enterprise_id="E111", team_id="T111", user_id="U111" ) self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNone(i) # delete all store.save(installation) store.delete_all(enterprise_id="E111", team_id="T111") i = store.find_installation(enterprise_id="E111", team_id="T111") self.assertIsNone(i) i = store.find_installation( enterprise_id="E111", team_id="T111", user_id="U111" ) self.assertIsNone(i) bot = store.find_bot(enterprise_id="E111", team_id="T222") self.assertIsNone(bot)
def _oauth(request: flask.Request, session: dict, user: Entity, dest: str, redirect_uri: str): # Retrieve the auth code and state from the request params if 'code' not in request.args: error = request.args["error"] if "error" in request.args else "" return flask.make_response( f"Something is wrong with the installation (error: {error})", 400) code = request.args['code'] # Verify the state parameter state_store = DatastoreOAuthStateStore(ds_util.client, STATE_EXPIRATION_SECONDS) if not state_store.consume(request.args["state"]): return flask.make_response( "Try the installation again (the state value is already expired)", 400) # Verify the state parameter # Complete the installation by calling oauth.v2.access API method client = WebClient() oauth_response = client.oauth_v2_access( client_id=config.slack_creds['client_id'], client_secret=config.slack_creds['client_secret'], redirect_uri=redirect_uri, code=code, ) # These seem to sometimes return None rather than being unset, so for maps, return {} installed_enterprise = oauth_response.get("enterprise", {}) or {} is_enterprise_install = oauth_response.get("is_enterprise_install") installed_team = oauth_response.get("team", {}) or {} installer = oauth_response.get("authed_user", {}) or {} incoming_webhook = oauth_response.get("incoming_webhook", {}) or {} bot_token = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id = None enterprise_url = None if bot_token is not None: auth_test = client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") installation = Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get("scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url"), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) # Store the installation service = Service.get(SERVICE_NAME, parent=user.key) store = DatastoreInstallationStore(ds_util.client, parent=service.key) store.save(installation) task_util.sync_service(Service.get(SERVICE_NAME, parent=user.key)) return flask.redirect(config.devserver_url + dest)
def test_org_installation(self): store = FileInstallationStore(client_id="111.222") installation = Installation( app_id="AO111", enterprise_id="EO111", user_id="UO111", bot_id="BO111", bot_token="xoxb-O111", bot_scopes=["chat:write"], bot_user_id="UO222", is_enterprise_install=True, ) store.save(installation) # find bots bot = store.find_bot(enterprise_id="EO111", team_id=None) self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="EO111", team_id="TO222", is_enterprise_install=True) self.assertIsNotNone(bot) bot = store.find_bot(enterprise_id="EO111", team_id="TO222") self.assertIsNone(bot) bot = store.find_bot(enterprise_id=None, team_id="TO111") self.assertIsNone(bot) # delete bots store.delete_bot(enterprise_id="EO111", team_id="TO222") bot = store.find_bot(enterprise_id="EO111", team_id=None) self.assertIsNotNone(bot) store.delete_bot(enterprise_id="EO111", team_id=None) bot = store.find_bot(enterprise_id="EO111", team_id=None) self.assertIsNone(bot) # find installations i = store.find_installation(enterprise_id="EO111", team_id=None) self.assertIsNotNone(i) i = store.find_installation(enterprise_id="EO111", team_id="T111", is_enterprise_install=True) self.assertIsNotNone(i) i = store.find_installation(enterprise_id="EO111", team_id="T222") self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T111") self.assertIsNone(i) i = store.find_installation(enterprise_id="EO111", team_id=None, user_id="UO111") self.assertIsNotNone(i) i = store.find_installation( enterprise_id="E111", team_id="T111", is_enterprise_install=True, user_id="U222", ) self.assertIsNone(i) i = store.find_installation(enterprise_id=None, team_id="T222", user_id="U111") self.assertIsNone(i) # delete installations store.delete_installation(enterprise_id="E111", team_id=None) i = store.find_installation(enterprise_id="E111", team_id=None) self.assertIsNone(i) # delete all store.save(installation) store.delete_all(enterprise_id="E111", team_id=None) i = store.find_installation(enterprise_id="E111", team_id=None) self.assertIsNone(i) i = store.find_installation(enterprise_id="E111", team_id=None, user_id="U111") self.assertIsNone(i) bot = store.find_bot(enterprise_id=None, team_id="T222") self.assertIsNone(bot)