Skip to content

Commit fdb440e

Browse files
committed
fix(oauth): encode authorization URL parameters
1 parent dd61579 commit fdb440e

2 files changed

Lines changed: 86 additions & 15 deletions

File tree

‎slack_sdk/oauth/authorize_url_generator/__init__.py‎

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional, Sequence
2+
from urllib.parse import urlencode
23

34

45
class AuthorizeUrlGenerator:
@@ -20,12 +21,18 @@ def __init__(
2021
def generate(self, state: str, team: Optional[str] = None) -> str:
2122
scopes = ",".join(self.scopes) if self.scopes else ""
2223
user_scopes = ",".join(self.user_scopes) if self.user_scopes else ""
23-
url = f"{self.authorization_url}?state={state}&client_id={self.client_id}&scope={scopes}&user_scope={user_scopes}"
24+
params = {
25+
"state": state,
26+
"client_id": self.client_id,
27+
"scope": scopes,
28+
"user_scope": user_scopes,
29+
}
2430
if self.redirect_uri is not None:
25-
url += f"&redirect_uri={self.redirect_uri}"
31+
params["redirect_uri"] = self.redirect_uri
2632
if team is not None:
27-
url += f"&team={team}"
28-
return url
33+
params["team"] = team
34+
query = urlencode(params, safe=":,/")
35+
return f"{self.authorization_url}?{query}"
2936

3037

3138
class OpenIDConnectAuthorizeUrlGenerator:
@@ -46,16 +53,16 @@ def __init__(
4653

4754
def generate(self, state: str, nonce: Optional[str] = None, team: Optional[str] = None) -> str:
4855
scopes = ",".join(self.scopes) if self.scopes else ""
49-
url = (
50-
f"{self.authorization_url}?"
51-
"response_type=code&"
52-
f"state={state}&"
53-
f"client_id={self.client_id}&"
54-
f"scope={scopes}&"
55-
f"redirect_uri={self.redirect_uri}"
56-
)
56+
params = {
57+
"response_type": "code",
58+
"state": state,
59+
"client_id": self.client_id,
60+
"scope": scopes,
61+
"redirect_uri": self.redirect_uri,
62+
}
5763
if team is not None:
58-
url += f"&team={team}"
64+
params["team"] = team
5965
if nonce is not None:
60-
url += f"&nonce={nonce}"
61-
return url
66+
params["nonce"] = nonce
67+
query = urlencode(params, safe=":,/")
68+
return f"{self.authorization_url}?{query}"

‎tests/slack_sdk/oauth/authorize_url_generator/test_generator.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from urllib.parse import parse_qs, urlsplit
23

34
from slack_sdk.oauth import AuthorizeUrlGenerator, OpenIDConnectAuthorizeUrlGenerator
45

@@ -70,3 +71,66 @@ def test_openid_connect(self):
7071
"&nonce=nnn"
7172
)
7273
self.assertEqual(expected, url)
74+
75+
def test_query_parameters_round_trip(self):
76+
redirect_uri = "https://www.example.com/callback?view=home&lang=en"
77+
generator = AuthorizeUrlGenerator(
78+
client_id="111.222",
79+
redirect_uri=redirect_uri,
80+
scopes=["chat:write", "commands"],
81+
user_scopes=["search:read"],
82+
)
83+
for state in (
84+
"",
85+
"plus+value",
86+
"space value",
87+
"key=value&other=value",
88+
"percent%20value",
89+
"fragment#value",
90+
"日本語",
91+
):
92+
with self.subTest(state=state):
93+
url = generator.generate(state=state, team="T12345")
94+
self.assertDictEqual(
95+
{
96+
"state": [state],
97+
"client_id": ["111.222"],
98+
"scope": ["chat:write,commands"],
99+
"user_scope": ["search:read"],
100+
"redirect_uri": [redirect_uri],
101+
"team": ["T12345"],
102+
},
103+
parse_qs(urlsplit(url).query, keep_blank_values=True),
104+
)
105+
106+
def test_openid_connect_query_parameters_round_trip(self):
107+
redirect_uri = "https://www.example.com/oidc/callback?view=home%20page&lang=en"
108+
generator = OpenIDConnectAuthorizeUrlGenerator(
109+
client_id="111.222",
110+
redirect_uri=redirect_uri,
111+
scopes=["openid", "profile"],
112+
)
113+
for value in (
114+
"",
115+
"plus+value",
116+
"space value",
117+
"key=value&other=value",
118+
"percent%20value",
119+
"fragment#value",
120+
"日本語",
121+
):
122+
with self.subTest(value=value):
123+
state = f"state-{value}"
124+
url = generator.generate(state=state, nonce=value, team="T12345")
125+
self.assertDictEqual(
126+
{
127+
"response_type": ["code"],
128+
"state": [state],
129+
"client_id": ["111.222"],
130+
"scope": ["openid,profile"],
131+
"redirect_uri": [redirect_uri],
132+
"team": ["T12345"],
133+
"nonce": [value],
134+
},
135+
parse_qs(urlsplit(url).query, keep_blank_values=True),
136+
)

0 commit comments

Comments
 (0)