Skip to content

Commit 89718cd

Browse files
committed
feat(oauth): support JSON payload for getting access token
It was previously used by qiita, but manually encoded.
1 parent dfb372f commit 89718cd

File tree

11 files changed

+111
-42
lines changed

11 files changed

+111
-42
lines changed

social_core/backends/azuread_b2c.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from __future__ import annotations
3131

32-
import json
32+
from json import dumps
3333
from typing import TYPE_CHECKING, Any, Literal, cast
3434

3535
from cryptography.hazmat.primitives import serialization
@@ -114,7 +114,8 @@ def request_access_token(
114114
url: str,
115115
method: Literal["GET", "POST", "DELETE"] = "GET",
116116
headers: Mapping[str, str | bytes] | None = None,
117-
data: dict | bytes | str | None = None,
117+
data: dict | None = None,
118+
json: dict | None = None,
118119
auth: tuple[str, str] | AuthBase | None = None,
119120
params: dict | None = None,
120121
) -> dict[Any, Any]:
@@ -125,7 +126,13 @@ def request_access_token(
125126
However, B2C backends provides `id_token`.
126127
"""
127128
response = super().request_access_token(
128-
url, method, headers, data, auth, params
129+
url,
130+
method=method,
131+
headers=headers,
132+
data=data,
133+
json=json,
134+
auth=auth,
135+
params=params,
129136
)
130137
if "access_token" not in response:
131138
response["access_token"] = response["id_token"]
@@ -145,7 +152,7 @@ def jwt_key_to_pem(self, key_json_dict):
145152
"""
146153
Builds a PEM formatted key string from a JWT public key dict.
147154
"""
148-
pub_key = RSAAlgorithm.from_jwk(json.dumps(key_json_dict))
155+
pub_key = RSAAlgorithm.from_jwk(dumps(key_json_dict))
149156

150157
# TODO: clarify the types of this; JWKs can apparently include both public and private,
151158
# but this code assumes public.

social_core/backends/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,14 @@ def uses_redirect(self) -> bool:
279279
otherwise return false."""
280280
return True
281281

282-
def request(
282+
def request( # noqa: PLR0913
283283
self,
284284
url: str,
285285
*,
286286
method: Literal["GET", "POST", "DELETE"] = "GET",
287287
headers: Mapping[str, str | bytes] | None = None,
288-
data: dict | bytes | str | None = None,
288+
data: dict | None = None,
289+
json: dict | None = None,
289290
auth: tuple[str, str] | AuthBase | None = None,
290291
params: dict | None = None,
291292
timeout: float | None = None,
@@ -308,6 +309,7 @@ def request(
308309
url,
309310
headers=headers,
310311
data=data,
312+
json=json,
311313
auth=auth,
312314
params=params,
313315
timeout=timeout,
@@ -319,12 +321,13 @@ def request(
319321
response.raise_for_status()
320322
return response
321323

322-
def get_json(
324+
def get_json( # noqa: PLR0913
323325
self,
324326
url: str,
325327
method: Literal["GET", "POST", "DELETE"] = "GET",
326328
headers: Mapping[str, str | bytes] | None = None,
327-
data: dict | bytes | str | None = None,
329+
data: dict | None = None,
330+
json: dict | None = None,
328331
auth: tuple[str, str] | AuthBase | None = None,
329332
params: dict | None = None,
330333
timeout: float | None = None,
@@ -334,6 +337,7 @@ def get_json(
334337
method=method,
335338
headers=headers,
336339
data=data,
340+
json=json,
337341
auth=auth,
338342
params=params,
339343
timeout=timeout,

social_core/backends/deezer.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ def request_access_token(
4242
url: str,
4343
method: Literal["GET", "POST", "DELETE"] = "GET",
4444
headers: Mapping[str, str | bytes] | None = None,
45-
data: dict | bytes | str | None = None,
45+
data: dict | None = None,
46+
json: dict | None = None,
4647
auth: tuple[str, str] | AuthBase | None = None,
4748
params: dict | None = None,
4849
) -> dict[Any, Any]:
4950
with wrap_access_token_error(self):
5051
response = self.request(
51-
url, method=method, headers=headers, data=data, auth=auth, params=params
52+
url,
53+
method=method,
54+
headers=headers,
55+
data=data,
56+
json=json,
57+
auth=auth,
58+
params=params,
5259
)
5360
return dict(parse_qsl(response.text))
5461

social_core/backends/linkedin.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import datetime
99
from calendar import timegm
10-
from typing import TYPE_CHECKING, Any, Literal, cast
10+
from typing import TYPE_CHECKING, Any, Literal
1111

1212
from social_core.backends.open_id_connect import OpenIdConnectAuth
1313
from social_core.exceptions import AuthCanceled, AuthTokenError
@@ -155,14 +155,21 @@ def request_access_token(
155155
url: str,
156156
method: Literal["GET", "POST", "DELETE"] = "GET",
157157
headers: Mapping[str, str | bytes] | None = None,
158-
data: dict | bytes | str | None = None,
158+
data: dict | None = None,
159+
json: dict | None = None,
159160
auth: tuple[str, str] | AuthBase | None = None,
160161
params: dict | None = None,
161162
) -> dict[Any, Any]:
162163
# LinkedIn expects a POST request with querystring parameters, despite
163164
# the spec http://tools.ietf.org/html/rfc6749#section-4.1.3
164165
return super().request_access_token(
165-
url, method, headers, data, auth, cast("dict", data)
166+
url,
167+
method=method,
168+
headers=headers,
169+
data=data,
170+
json=json,
171+
auth=auth,
172+
params=data,
166173
)
167174

168175
def process_error(self, data) -> None:

social_core/backends/loginradius.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,19 @@ def request_access_token(
4646
url: str,
4747
method: Literal["GET", "POST", "DELETE"] = "GET",
4848
headers: Mapping[str, str | bytes] | None = None,
49-
data: dict | bytes | str | None = None,
49+
data: dict | None = None,
50+
json: dict | None = None,
5051
auth: tuple[str, str] | AuthBase | None = None,
5152
params: dict | None = None,
5253
) -> dict[Any, Any]:
5354
return super().request_access_token(
5455
url,
55-
method,
56-
headers,
57-
data,
58-
auth,
59-
{
56+
method=method,
57+
headers=headers,
58+
data=data,
59+
json=json,
60+
auth=auth,
61+
params={
6062
"token": self.data.get("token"),
6163
"secret": self.setting("SECRET"),
6264
},

social_core/backends/oauth.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import base64
44
import hashlib
55
from typing import TYPE_CHECKING, Any, Literal, cast
6-
from urllib.parse import urlencode
76

87
from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER
98
from requests_oauthlib import OAuth1
@@ -52,6 +51,7 @@ class OAuthAuth(BaseAuth):
5251
AUTHORIZATION_URL = ""
5352
ACCESS_TOKEN_URL = ""
5453
ACCESS_TOKEN_METHOD: Literal["GET", "POST"] = "POST"
54+
ACCESS_TOKEN_PAYLOAD: Literal["form", "json"] = "form"
5555
REVOKE_TOKEN_URL: str = ""
5656
REVOKE_TOKEN_METHOD: Literal["GET", "POST", "DELETE"] = "POST"
5757
ID_KEY = "id"
@@ -180,7 +180,7 @@ def revoke_token(self, token, uid):
180180
if revoke_token_url := self.revoke_token_url(token, uid):
181181
params = self.revoke_token_params(token, uid)
182182
headers = self.revoke_token_headers(token, uid)
183-
data = urlencode(params) if self.REVOKE_TOKEN_METHOD != "GET" else None
183+
data = params if self.REVOKE_TOKEN_METHOD != "GET" else None
184184
response = self.request(
185185
revoke_token_url,
186186
params=params,
@@ -420,7 +420,9 @@ def auth_complete_credentials(self):
420420

421421
def auth_headers(self) -> Mapping[str, str | bytes]:
422422
return {
423-
"Content-Type": "application/x-www-form-urlencoded",
423+
"Content-Type": "application/json"
424+
if self.ACCESS_TOKEN_PAYLOAD == "json"
425+
else "application/x-www-form-urlencoded",
424426
"Accept": "application/json",
425427
}
426428

@@ -445,13 +447,20 @@ def request_access_token(
445447
url: str,
446448
method: Literal["GET", "POST", "DELETE"] = "GET",
447449
headers: Mapping[str, str | bytes] | None = None,
448-
data: dict | bytes | str | None = None,
450+
data: dict | None = None,
451+
json: dict | None = None,
449452
auth: tuple[str, str] | AuthBase | None = None,
450453
params: dict | None = None,
451454
) -> dict[Any, Any]:
452455
with wrap_access_token_error(self):
453456
return self.get_json(
454-
url, method=method, headers=headers, data=data, auth=auth, params=params
457+
url,
458+
method=method,
459+
headers=headers,
460+
data=data,
461+
auth=auth,
462+
params=params,
463+
json=json,
455464
)
456465

457466
def process_error(self, data) -> None:
@@ -467,15 +476,19 @@ def auth_complete(self, *args, **kwargs):
467476
"""Completes login process, must return user instance"""
468477
self.process_error(self.data)
469478
state = self.validate_state()
470-
data, params = None, None
479+
data = params = json = None
480+
auth_params = self.auth_complete_params(state)
471481
if self.ACCESS_TOKEN_METHOD == "GET":
472-
params = self.auth_complete_params(state)
482+
params = auth_params
483+
elif self.ACCESS_TOKEN_PAYLOAD == "json":
484+
json = auth_params
473485
else:
474-
data = self.auth_complete_params(state)
486+
data = auth_params
475487

476488
response = self.request_access_token(
477489
self.access_token_url(),
478490
data=data,
491+
json=json,
479492
params=params,
480493
headers=self.auth_headers(),
481494
auth=self.auth_complete_credentials(),

social_core/backends/open_id_connect.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import base64
44
import datetime
5-
import json
65
from calendar import timegm
6+
from json import loads
77
from typing import TYPE_CHECKING, Any, Literal, cast
88

99
import jwt
@@ -161,7 +161,7 @@ def get_jwks_keys(self):
161161

162162
def get_remote_jwks_keys(self):
163163
response = self.request(self.jwks_uri())
164-
return json.loads(response.text)["keys"]
164+
return loads(response.text)["keys"]
165165

166166
def auth_params(self, state=None): # noqa: C901, PLR0912
167167
"""Return extra arguments needed on auth process."""
@@ -349,7 +349,8 @@ def request_access_token(
349349
url: str,
350350
method: Literal["GET", "POST", "DELETE"] = "GET",
351351
headers: Mapping[str, str | bytes] | None = None,
352-
data: dict | bytes | str | None = None,
352+
data: dict | None = None,
353+
json: dict | None = None,
353354
auth: tuple[str, str] | AuthBase | None = None,
354355
params: dict | None = None,
355356
) -> dict[Any, Any]:
@@ -358,7 +359,13 @@ def request_access_token(
358359
store it (temporarily).
359360
"""
360361
response = super().request_access_token(
361-
url, method, headers, data, auth, params
362+
url,
363+
method=method,
364+
headers=headers,
365+
data=data,
366+
json=json,
367+
auth=auth,
368+
params=params,
362369
)
363370
self.id_token = self.validate_and_return_id_token(
364371
response["id_token"], response["access_token"]

social_core/backends/qiita.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class QiitaOAuth2(BaseOAuth2):
2626

2727
AUTHORIZATION_URL = "https://qiita.com/api/v2/oauth/authorize"
2828
ACCESS_TOKEN_URL = "https://qiita.com/api/v2/access_tokens"
29+
ACCESS_TOKEN_PAYLOAD = "json"
2930
SCOPE_SEPARATOR = " "
3031
REDIRECT_STATE = True
3132
EXTRA_DATA = [
@@ -57,19 +58,25 @@ def auth_complete_params(self, state=None):
5758
del data["redirect_uri"]
5859
return data
5960

60-
def auth_headers(self):
61-
return {"Content-Type": "application/json"}
62-
6361
def request_access_token(
6462
self,
6563
url: str,
6664
method: Literal["GET", "POST", "DELETE"] = "GET",
6765
headers: Mapping[str, str | bytes] | None = None,
68-
data: dict | bytes | str | None = None,
66+
data: dict | None = None,
67+
json: dict | None = None,
6968
auth: tuple[str, str] | AuthBase | None = None,
7069
params: dict | None = None,
7170
) -> dict[Any, Any]:
72-
data = super().request_access_token(url, method, headers, data, auth, params)
71+
data = super().request_access_token(
72+
url=url,
73+
method=method,
74+
headers=headers,
75+
data=data,
76+
json=json,
77+
auth=auth,
78+
params=params,
79+
)
7380
data.update({"access_token": data["token"]})
7481
return data
7582

social_core/backends/qq.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from __future__ import annotations
88

9-
import json
9+
from json import loads
1010
from typing import TYPE_CHECKING, Any, Literal
1111

1212
from social_core.utils import parse_qs, wrap_access_token_error
@@ -58,7 +58,7 @@ def get_user_details(self, response):
5858
def get_openid(self, access_token):
5959
response = self.request(self.OPENID_URL, params={"access_token": access_token})
6060
content = response.content.decode()
61-
data = json.loads(content[10:-3])
61+
data = loads(content[10:-3])
6262
return data["openid"]
6363

6464
def user_data(self, access_token: str, *args, **kwargs) -> dict[str, Any] | None:
@@ -79,12 +79,19 @@ def request_access_token(
7979
url: str,
8080
method: Literal["GET", "POST", "DELETE"] = "GET",
8181
headers: Mapping[str, str | bytes] | None = None,
82-
data: dict | bytes | str | None = None,
82+
data: dict | None = None,
83+
json: dict | None = None,
8384
auth: tuple[str, str] | AuthBase | None = None,
8485
params: dict | None = None,
8586
) -> dict[Any, Any]:
8687
with wrap_access_token_error(self):
8788
response = self.request(
88-
url, method=method, headers=headers, data=data, auth=auth, params=params
89+
url,
90+
method=method,
91+
headers=headers,
92+
data=data,
93+
json=json,
94+
auth=auth,
95+
params=params,
8996
)
9097
return parse_qs(response.content)

0 commit comments

Comments
 (0)