Ejemplo n.º 1
0
 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()
Ejemplo n.º 2
0
    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())
Ejemplo n.º 3
0
    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")
Ejemplo n.º 4
0
 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)
Ejemplo n.º 5
0
 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)
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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})")
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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
Ejemplo n.º 15
0
 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",
     )
Ejemplo n.º 16
0
    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)
Ejemplo n.º 19
0
 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(),
     )
Ejemplo n.º 20
0
    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(),
        )
Ejemplo n.º 21
0
    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
            )
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
    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")
Ejemplo n.º 25
0
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})"
    )
Ejemplo n.º 26
0
    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)
Ejemplo n.º 27
0
    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)
Ejemplo n.º 28
0
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)
Ejemplo n.º 29
0
    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)