Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions integration_tests/samples/oauth/oauth_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,19 @@ def oauth_callback():

@app.route("/slack/events", methods=["POST"])
def slack_app():
data = request.get_data()
if not signature_verifier.is_valid(
body=request.get_data(),
body=data,
timestamp=request.headers.get("X-Slack-Request-Timestamp"),
signature=request.headers.get("X-Slack-Signature"),
):
return make_response("invalid request", 403)

if data and b"url_verification" in data:
body = json.loads(data)
if body.get("type") == "url_verification" and "challenge" in body:
return make_response(body["challenge"], 200)

if "command" in request.form and request.form["command"] == "/open-modal":
try:
enterprise_id = request.form.get("enterprise_id")
Expand Down Expand Up @@ -193,4 +199,4 @@ def slack_app():

# python3 integration_tests/samples/oauth/oauth_v2.py
# ngrok http 3000
# https://{yours}.ngrok.io/slack/oauth/start
# https://{yours}.ngrok.io/slack/install
8 changes: 7 additions & 1 deletion integration_tests/samples/oauth/oauth_v2_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,19 @@ async def oauth_callback(req: Request):

@app.post("/slack/events")
async def slack_app(req: Request):
data = req.body.decode("utf-8")
if not signature_verifier.is_valid(
body=req.body.decode("utf-8"),
body=data,
timestamp=req.headers.get("X-Slack-Request-Timestamp"),
signature=req.headers.get("X-Slack-Signature"),
):
return HTTPResponse(status=403, body="invalid request")

if data and "url_verification" in data:
body = json.loads(data)
if body.get("type") == "url_verification" and "challenge" in body:
return HTTPResponse(status=200, body=body["challenge"])

if "command" in req.form and req.form.get("command") == "/open-modal":
try:
enterprise_id = req.form.get("enterprise_id")
Expand Down
8 changes: 5 additions & 3 deletions slack_sdk/oauth/installation_store/models/bot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone
from time import time
from typing import Optional, Union, Dict, Any, Sequence

Expand Down Expand Up @@ -100,10 +100,12 @@ def _to_standard_value_dict(self) -> Dict[str, Any]:
"bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
"bot_refresh_token": self.bot_refresh_token,
"bot_token_expires_at": (
datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None
datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc)
if self.bot_token_expires_at is not None
else None
),
"is_enterprise_install": self.is_enterprise_install,
"installed_at": datetime.utcfromtimestamp(self.installed_at),
"installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc),
Comment on lines -106 to +108
Copy link
Member

Choose a reason for hiding this comment

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

🤩 praise: @WilliamBergamin I'm a huge fan of keeping all representations of time in the UTC timezone!

👁️‍🗨️ question: Would adjacent implementations also require a similar change? I find this initial value might be generated with an offset:

if installed_at is None:
self.installed_at = datetime.now().timestamp()
else:
self.installed_at = _timestamp_to_type(installed_at, float)

📚 https://docs.python.org/3/library/datetime.html#datetime.datetime.now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its better if we open up another PR for these changes and resolve #1731

}

def to_dict_for_copying(self) -> Dict[str, Any]:
Expand Down
12 changes: 8 additions & 4 deletions slack_sdk/oauth/installation_store/models/installation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone
from time import time
from typing import Optional, Union, Dict, Any, Sequence

Expand Down Expand Up @@ -173,22 +173,26 @@ def _to_standard_value_dict(self) -> Dict[str, Any]:
"bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
"bot_refresh_token": self.bot_refresh_token,
"bot_token_expires_at": (
datetime.utcfromtimestamp(self.bot_token_expires_at) if self.bot_token_expires_at is not None else None
datetime.fromtimestamp(self.bot_token_expires_at, tz=timezone.utc)
if self.bot_token_expires_at is not None
else None
),
"user_id": self.user_id,
"user_token": self.user_token,
"user_scopes": ",".join(self.user_scopes) if self.user_scopes else None,
"user_refresh_token": self.user_refresh_token,
"user_token_expires_at": (
datetime.utcfromtimestamp(self.user_token_expires_at) if self.user_token_expires_at is not None else None
datetime.fromtimestamp(self.user_token_expires_at, tz=timezone.utc)
if self.user_token_expires_at is not None
else None
),
"incoming_webhook_url": self.incoming_webhook_url,
"incoming_webhook_channel": self.incoming_webhook_channel,
"incoming_webhook_channel_id": self.incoming_webhook_channel_id,
"incoming_webhook_configuration_url": self.incoming_webhook_configuration_url,
"is_enterprise_install": self.is_enterprise_install,
"token_type": self.token_type,
"installed_at": datetime.utcfromtimestamp(self.installed_at),
"installed_at": datetime.fromtimestamp(self.installed_at, tz=timezone.utc),
}

def to_dict_for_copying(self) -> Dict[str, Any]:
Expand Down
10 changes: 5 additions & 5 deletions slack_sdk/oauth/state_store/sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import time
from datetime import datetime
from datetime import datetime, timezone
from logging import Logger
from uuid import uuid4

Expand Down Expand Up @@ -55,7 +55,7 @@ def logger(self) -> Logger:

def issue(self, *args, **kwargs) -> str:
state: str = str(uuid4())
now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
with self.engine.begin() as conn:
conn.execute(
self.oauth_states.insert(),
Expand All @@ -67,7 +67,7 @@ def consume(self, state: str) -> bool:
try:
with self.engine.begin() as conn:
c = self.oauth_states.c
query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow()))
query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc)))
result = conn.execute(query)
for row in result.mappings():
self.logger.debug(f"consume's query result: {row}")
Expand Down Expand Up @@ -124,7 +124,7 @@ def logger(self) -> Logger:

async def async_issue(self, *args, **kwargs) -> str:
state: str = str(uuid4())
now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
now = datetime.fromtimestamp(time.time() + self.expiration_seconds, tz=timezone.utc)
async with self.engine.begin() as conn:
await conn.execute(
self.oauth_states.insert(),
Expand All @@ -136,7 +136,7 @@ async def async_consume(self, state: str) -> bool:
try:
async with self.engine.begin() as conn:
c = self.oauth_states.c
query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.utcnow()))
query = self.oauth_states.select().where(and_(c.state == state, c.expire_at > datetime.now(tz=timezone.utc)))
result = await conn.execute(query)
for row in result.mappings():
self.logger.debug(f"consume's query result: {row}")
Expand Down
43 changes: 43 additions & 0 deletions tests/slack_sdk/oauth/installation_store/test_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
from datetime import datetime, timezone
import unittest

from slack_sdk.oauth.installation_store import Installation, FileInstallationStore, Bot
Expand Down Expand Up @@ -36,6 +37,22 @@ def test_bot_custom_fields(self):
self.assertEqual(bot.to_dict().get("service_user_id"), "XYZ123")
self.assertEqual(bot.to_dict_for_copying().get("custom_values").get("service_user_id"), "XYZ123")

def test_bot_datetime_manipulation(self):
expected_timestamp = datetime.now(tz=timezone.utc)
bot = Bot(
bot_token="xoxb-",
bot_id="B111",
bot_user_id="U111",
bot_token_expires_at=expected_timestamp,
installed_at=expected_timestamp,
)
bot_dict = bot.to_dict()
self.assertIsNotNone(bot_dict)
self.assertEqual(
bot_dict.get("bot_token_expires_at").isoformat(), expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00")
)
self.assertEqual(bot_dict.get("installed_at"), expected_timestamp)

def test_installation(self):
installation = Installation(
app_id="A111",
Expand Down Expand Up @@ -84,3 +101,29 @@ def test_installation_custom_fields(self):
self.assertEqual(bot.to_dict().get("app_id"), "A111")
self.assertEqual(bot.to_dict().get("service_user_id"), "XYZ123")
self.assertEqual(bot.to_dict_for_copying().get("custom_values").get("app_id"), "A222")

def test_installation_datetime_manipulation(self):
expected_timestamp = datetime.now(tz=timezone.utc)
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",
bot_token_expires_at=expected_timestamp,
user_token_expires_at=expected_timestamp,
installed_at=expected_timestamp,
)
installation_dict = installation.to_dict()
self.assertIsNotNone(installation_dict)
self.assertEqual(
installation_dict.get("bot_token_expires_at").isoformat(), expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00")
)
self.assertEqual(
installation_dict.get("user_token_expires_at").isoformat(),
expected_timestamp.strftime("%Y-%m-%dT%H:%M:%S+00:00"),
)
self.assertEqual(installation_dict.get("installed_at"), expected_timestamp)