Skip to content

Commit 4ecce6b

Browse files
committed
Add cache_enabled option to InstallationStoreAuthorize
1 parent 495e8e7 commit 4ecce6b

File tree

6 files changed

+226
-4
lines changed

6 files changed

+226
-4
lines changed

slack_bolt/authorization/async_authorize.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,18 @@ async def __call__(
9191

9292

9393
class AsyncInstallationStoreAuthorize(AsyncAuthorize):
94+
authorize_result_cache: Dict[str, AuthorizeResult] = {}
95+
9496
def __init__(
95-
self, *, logger: Logger, installation_store: AsyncInstallationStore,
97+
self,
98+
*,
99+
logger: Logger,
100+
installation_store: AsyncInstallationStore,
101+
cache_enabled: bool = False,
96102
):
97103
self.logger = logger
98104
self.installation_store = installation_store
105+
self.cache_enabled = cache_enabled
99106

100107
async def __call__(
101108
self,
@@ -115,13 +122,18 @@ async def __call__(
115122
)
116123
return None
117124

125+
if self.cache_enabled and bot.bot_token in self.authorize_result_cache:
126+
return self.authorize_result_cache[bot.bot_token]
118127
try:
119128
auth_result = await context.client.auth_test(token=bot.bot_token)
120-
return AuthorizeResult.from_auth_test_response(
129+
authorize_result = AuthorizeResult.from_auth_test_response(
121130
auth_test_response=auth_result,
122131
bot_token=bot.bot_token,
123132
user_token=None, # Not yet supported
124133
)
134+
if self.cache_enabled:
135+
self.authorize_result_cache[bot.bot_token] = authorize_result
136+
return authorize_result
125137
except SlackApiError as err:
126138
self.logger.debug(
127139
f"The stored bot token for enterprise_id: {enterprise_id} team_id: {team_id} "

slack_bolt/authorization/authorize.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,18 @@ def __call__(
8989

9090

9191
class InstallationStoreAuthorize(Authorize):
92+
authorize_result_cache: Dict[str, AuthorizeResult] = {}
93+
9294
def __init__(
93-
self, *, logger: Logger, installation_store: InstallationStore,
95+
self,
96+
*,
97+
logger: Logger,
98+
installation_store: InstallationStore,
99+
cache_enabled: bool = False,
94100
):
95101
self.logger = logger
96102
self.installation_store = installation_store
103+
self.cache_enabled = cache_enabled
97104

98105
def __call__(
99106
self,
@@ -113,13 +120,18 @@ def __call__(
113120
)
114121
return None
115122

123+
if self.cache_enabled and bot.bot_token in self.authorize_result_cache:
124+
return self.authorize_result_cache[bot.bot_token]
116125
try:
117126
auth_result = context.client.auth_test(token=bot.bot_token)
118-
return AuthorizeResult.from_auth_test_response(
127+
authorize_result = AuthorizeResult.from_auth_test_response(
119128
auth_test_response=auth_result,
120129
bot_token=bot.bot_token,
121130
user_token=None, # Not yet supported
122131
)
132+
if self.cache_enabled:
133+
self.authorize_result_cache[bot.bot_token] = authorize_result
134+
return authorize_result
123135
except SlackApiError as err:
124136
self.logger.debug(
125137
f"The stored bot token for enterprise_id: {enterprise_id} team_id: {team_id} "

tests/slack_bolt/authorization/__init__.py

Whitespace-only changes.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import datetime
2+
import logging
3+
from logging import Logger
4+
from typing import Optional
5+
6+
from slack_sdk import WebClient
7+
from slack_sdk.oauth import InstallationStore
8+
from slack_sdk.oauth.installation_store import Bot, Installation
9+
10+
from slack_bolt import BoltContext
11+
from slack_bolt.authorization.authorize import InstallationStoreAuthorize
12+
from tests.mock_web_api_server import (
13+
cleanup_mock_web_api_server,
14+
setup_mock_web_api_server,
15+
)
16+
17+
18+
class TestAuthorize:
19+
mock_api_server_base_url = "http://localhost:8888"
20+
21+
def setup_method(self):
22+
setup_mock_web_api_server(self)
23+
24+
def teardown_method(self):
25+
cleanup_mock_web_api_server(self)
26+
27+
def test_installation_store(self):
28+
installation_store = MemoryInstallationStore()
29+
authorize = InstallationStoreAuthorize(
30+
logger=installation_store.logger, installation_store=installation_store
31+
)
32+
context = BoltContext()
33+
context["client"] = WebClient(base_url=self.mock_api_server_base_url)
34+
result = authorize(
35+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
36+
)
37+
assert result.bot_id == "BZYBOTHED"
38+
assert result.bot_user_id == "W23456789"
39+
assert self.mock_received_requests["/auth.test"] == 1
40+
41+
result = authorize(
42+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
43+
)
44+
assert result.bot_id == "BZYBOTHED"
45+
assert result.bot_user_id == "W23456789"
46+
assert self.mock_received_requests["/auth.test"] == 2
47+
48+
def test_installation_store_cached(self):
49+
installation_store = MemoryInstallationStore()
50+
authorize = InstallationStoreAuthorize(
51+
logger=installation_store.logger,
52+
installation_store=installation_store,
53+
cache_enabled=True,
54+
)
55+
context = BoltContext()
56+
context["client"] = WebClient(base_url=self.mock_api_server_base_url)
57+
result = authorize(
58+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
59+
)
60+
assert result.bot_id == "BZYBOTHED"
61+
assert result.bot_user_id == "W23456789"
62+
assert self.mock_received_requests["/auth.test"] == 1
63+
64+
result = authorize(
65+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
66+
)
67+
assert result.bot_id == "BZYBOTHED"
68+
assert result.bot_user_id == "W23456789"
69+
assert self.mock_received_requests["/auth.test"] == 1 # cached
70+
71+
72+
class MemoryInstallationStore(InstallationStore):
73+
@property
74+
def logger(self) -> Logger:
75+
return logging.getLogger(__name__)
76+
77+
def save(self, installation: Installation):
78+
pass
79+
80+
def find_bot(
81+
self, *, enterprise_id: Optional[str], team_id: Optional[str]
82+
) -> Optional[Bot]:
83+
return Bot(
84+
app_id="A111",
85+
enterprise_id="E111",
86+
team_id="T0G9PQBBK",
87+
bot_token="xoxb-valid",
88+
bot_id="B",
89+
bot_user_id="W",
90+
bot_scopes=["commands", "chat:write"],
91+
installed_at=datetime.datetime.now().timestamp(),
92+
)

tests/slack_bolt_async/authorization/__init__.py

Whitespace-only changes.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import asyncio
2+
import datetime
3+
import logging
4+
from logging import Logger
5+
from typing import Optional
6+
7+
import pytest
8+
from slack_sdk.oauth.installation_store import Bot, Installation
9+
from slack_sdk.oauth.installation_store.async_installation_store import (
10+
AsyncInstallationStore,
11+
)
12+
from slack_sdk.web.async_client import AsyncWebClient
13+
14+
from slack_bolt.authorization.async_authorize import AsyncInstallationStoreAuthorize
15+
from slack_bolt.context.async_context import AsyncBoltContext
16+
from tests.mock_web_api_server import (
17+
setup_mock_web_api_server,
18+
cleanup_mock_web_api_server,
19+
)
20+
from tests.utils import remove_os_env_temporarily, restore_os_env
21+
22+
23+
class TestAsyncAuthorize:
24+
mock_api_server_base_url = "http://localhost:8888"
25+
client = AsyncWebClient(base_url=mock_api_server_base_url,)
26+
27+
@pytest.fixture
28+
def event_loop(self):
29+
old_os_env = remove_os_env_temporarily()
30+
try:
31+
setup_mock_web_api_server(self)
32+
loop = asyncio.get_event_loop()
33+
yield loop
34+
loop.close()
35+
cleanup_mock_web_api_server(self)
36+
finally:
37+
restore_os_env(old_os_env)
38+
39+
@pytest.mark.asyncio
40+
async def test_installation_store(self):
41+
installation_store = MemoryInstallationStore()
42+
authorize = AsyncInstallationStoreAuthorize(
43+
logger=installation_store.logger, installation_store=installation_store
44+
)
45+
context = AsyncBoltContext()
46+
context["client"] = self.client
47+
result = await authorize(
48+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
49+
)
50+
assert result.bot_id == "BZYBOTHED"
51+
assert result.bot_user_id == "W23456789"
52+
assert self.mock_received_requests["/auth.test"] == 1
53+
54+
result = await authorize(
55+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
56+
)
57+
assert result.bot_id == "BZYBOTHED"
58+
assert result.bot_user_id == "W23456789"
59+
assert self.mock_received_requests["/auth.test"] == 2
60+
61+
@pytest.mark.asyncio
62+
async def test_installation_store_cached(self):
63+
installation_store = MemoryInstallationStore()
64+
authorize = AsyncInstallationStoreAuthorize(
65+
logger=installation_store.logger,
66+
installation_store=installation_store,
67+
cache_enabled=True,
68+
)
69+
context = AsyncBoltContext()
70+
context["client"] = self.client
71+
result = await authorize(
72+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
73+
)
74+
assert result.bot_id == "BZYBOTHED"
75+
assert result.bot_user_id == "W23456789"
76+
assert self.mock_received_requests["/auth.test"] == 1
77+
78+
result = await authorize(
79+
context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
80+
)
81+
assert result.bot_id == "BZYBOTHED"
82+
assert result.bot_user_id == "W23456789"
83+
assert self.mock_received_requests["/auth.test"] == 1 # cached
84+
85+
86+
class MemoryInstallationStore(AsyncInstallationStore):
87+
@property
88+
def logger(self) -> Logger:
89+
return logging.getLogger(__name__)
90+
91+
async def async_save(self, installation: Installation):
92+
pass
93+
94+
async def async_find_bot(
95+
self, *, enterprise_id: Optional[str], team_id: Optional[str]
96+
) -> Optional[Bot]:
97+
return Bot(
98+
app_id="A111",
99+
enterprise_id="E111",
100+
team_id="T0G9PQBBK",
101+
bot_token="xoxb-valid",
102+
bot_id="B",
103+
bot_user_id="W",
104+
bot_scopes=["commands", "chat:write"],
105+
installed_at=datetime.datetime.now().timestamp(),
106+
)

0 commit comments

Comments
 (0)