Skip to content

Commit 58e8c78

Browse files
Merge pull request #404 from supertokens/provider-fixes
fix: bitbucket gitlab impl and other fixes
2 parents 97f8bd5 + e1eca1e commit 58e8c78

File tree

18 files changed

+547
-132
lines changed

18 files changed

+547
-132
lines changed

supertokens_python/recipe/multitenancy/recipe.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
from supertokens_python.recipe.session.claim_base_classes.primitive_array_claim import (
2121
PrimitiveArrayClaim,
2222
)
23-
from supertokens_python.recipe.session.interfaces import JSONObject
2423
from supertokens_python.recipe_module import APIHandled, RecipeModule
25-
from supertokens_python.utils import get_timestamp_ms
2624

2725
from ...post_init_callbacks import PostSTInitCallbacks
2826

@@ -207,26 +205,5 @@ async def fetch_value(
207205

208206
super().__init__("st-t-dmns", fetch_value, default_max_age_in_sec)
209207

210-
def get_value_from_payload(
211-
self, payload: JSONObject, user_context: Optional[Dict[str, Any]] = None
212-
) -> Optional[List[str]]:
213-
_ = user_context
214-
215-
res = payload.get(self.key, {}).get("v")
216-
if res is None:
217-
return []
218-
return res
219-
220-
def get_last_refetch_time(
221-
self, payload: JSONObject, user_context: Optional[Dict[str, Any]] = None
222-
) -> Optional[int]:
223-
_ = user_context
224-
225-
res = payload.get(self.key, {}).get("t")
226-
if res is None:
227-
return get_timestamp_ms()
228-
229-
return res
230-
231208

232209
AllowedDomainsClaim = AllowedDomainsClaimClass()

supertokens_python/recipe/thirdparty/provider.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,7 @@ def __init__(
155155
third_party_id: str,
156156
name: Optional[str] = None,
157157
authorization_endpoint: Optional[str] = None,
158-
authorization_endpoint_query_params: Optional[
159-
Dict[str, Union[str, None]]
160-
] = None,
158+
authorization_endpoint_query_params: Optional[Dict[str, Any]] = None,
161159
token_endpoint: Optional[str] = None,
162160
token_endpoint_body_params: Optional[Dict[str, Union[str, None]]] = None,
163161
user_info_endpoint: Optional[str] = None,

supertokens_python/recipe/thirdparty/providers/active_directory.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,4 @@ def ActiveDirectory(
5454
if input.config.name is None:
5555
input.config.name = "Active Directory"
5656

57-
if input.config.user_info_map is None:
58-
input.config.user_info_map = UserInfoMap(UserFields(), UserFields())
59-
60-
if input.config.user_info_map.from_id_token_payload.user_id is None:
61-
input.config.user_info_map.from_id_token_payload.user_id = "sub"
62-
63-
if input.config.user_info_map.from_id_token_payload.email is None:
64-
input.config.user_info_map.from_id_token_payload.email = "email"
65-
6657
return NewProvider(input, ActiveDirectoryImpl)

supertokens_python/recipe/thirdparty/providers/bitbucket.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,79 @@
1414

1515
from __future__ import annotations
1616

17-
from supertokens_python.recipe.thirdparty.provider import Provider
17+
from typing import Dict, Any, Optional
18+
19+
from supertokens_python.recipe.thirdparty.provider import (
20+
ProviderConfigForClient,
21+
ProviderInput,
22+
Provider,
23+
)
1824
from .custom import GenericProvider, NewProvider
19-
from ..provider import Provider, ProviderInput
25+
26+
from .utils import do_get_request
27+
from ..types import RawUserInfoFromProvider, UserInfo, UserInfoEmail
2028

2129

2230
class BitbucketImpl(GenericProvider):
23-
pass
31+
async def get_config_for_client_type(
32+
self, client_type: Optional[str], user_context: Dict[str, Any]
33+
) -> ProviderConfigForClient:
34+
config = await super().get_config_for_client_type(client_type, user_context)
35+
36+
if config.scope is None:
37+
config.scope = ["account", "email"]
38+
39+
return config
40+
41+
async def get_user_info(
42+
self, oauth_tokens: Dict[str, Any], user_context: Dict[str, Any]
43+
) -> UserInfo:
44+
_ = user_context
45+
access_token = oauth_tokens.get("access_token")
46+
if access_token is None:
47+
raise Exception("Access token not found")
48+
49+
headers = {
50+
"Authorization": f"Bearer {access_token}",
51+
}
52+
53+
raw_user_info_from_provider = RawUserInfoFromProvider({}, {})
54+
55+
user_info_from_access_token = await do_get_request(
56+
"https://api.bitbucket.org/2.0/user",
57+
query_params=None,
58+
headers=headers,
59+
)
60+
61+
raw_user_info_from_provider.from_user_info_api = user_info_from_access_token
62+
63+
user_info_from_email = await do_get_request(
64+
"https://api.bitbucket.org/2.0/user/emails",
65+
query_params=None,
66+
headers=headers,
67+
)
68+
69+
if raw_user_info_from_provider.from_id_token_payload is None:
70+
# Actually this should never happen but python type
71+
# checker is not agreeing so doing this:
72+
raw_user_info_from_provider.from_id_token_payload = {}
73+
74+
raw_user_info_from_provider.from_id_token_payload[
75+
"email"
76+
] = user_info_from_email
77+
78+
email = None
79+
is_verified = False
80+
for email_info in user_info_from_email.values():
81+
if email_info["is_primary"]:
82+
email = email_info["email"]
83+
is_verified = email_info["is_confirmed"]
84+
85+
return UserInfo(
86+
third_party_user_id=raw_user_info_from_provider.from_user_info_api["uuid"],
87+
email=None if email is None else UserInfoEmail(email, is_verified),
88+
raw_user_info_from_provider=raw_user_info_from_provider,
89+
)
2490

2591

2692
def Bitbucket(input: ProviderInput) -> Provider: # pylint: disable=redefined-builtin
@@ -35,7 +101,9 @@ def Bitbucket(input: ProviderInput) -> Provider: # pylint: disable=redefined-bu
35101
if input.config.token_endpoint is None:
36102
input.config.token_endpoint = "https://bitbucket.org/site/oauth2/access_token"
37103

38-
if input.config.user_info_endpoint is None:
39-
input.config.user_info_endpoint = "https://api.bitbucket.org/2.0/user"
104+
if input.config.authorization_endpoint_query_params is None:
105+
input.config.authorization_endpoint_query_params = {
106+
"audience": "api.atlassian.com",
107+
}
40108

41109
return NewProvider(input, BitbucketImpl)

supertokens_python/recipe/thirdparty/providers/config_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from supertokens_python.normalised_url_path import NormalisedURLPath
55
from .active_directory import ActiveDirectory
66
from .apple import Apple
7+
from .bitbucket import Bitbucket
78
from .boxy_saml import BoxySAML
89
from .discord import Discord
910
from .facebook import Facebook
1011
from .github import Github
12+
from .gitlab import Gitlab
1113
from .google_workspaces import GoogleWorkspaces
1214
from .google import Google
1315
from .linkedin import Linkedin
@@ -176,12 +178,16 @@ def create_provider(provider_input: ProviderInput) -> Provider:
176178
return ActiveDirectory(provider_input)
177179
if provider_input.config.third_party_id.startswith("apple"):
178180
return Apple(provider_input)
181+
if provider_input.config.third_party_id.startswith("bitbucket"):
182+
return Bitbucket(provider_input)
179183
if provider_input.config.third_party_id.startswith("discord"):
180184
return Discord(provider_input)
181185
if provider_input.config.third_party_id.startswith("facebook"):
182186
return Facebook(provider_input)
183187
if provider_input.config.third_party_id.startswith("github"):
184188
return Github(provider_input)
189+
if provider_input.config.third_party_id.startswith("gitlab"):
190+
return Gitlab(provider_input)
185191
if provider_input.config.third_party_id.startswith("google-workspaces"):
186192
return GoogleWorkspaces(provider_input)
187193
if provider_input.config.third_party_id.startswith("google"):

supertokens_python/recipe/thirdparty/providers/custom.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ def _normalize_input( # pylint: disable=no-self-use
215215
from_user_info_api=UserFields(),
216216
)
217217

218+
# These are safe defaults common to most providers. Each provider
219+
# implementations override these as necessary
218220
if input_config.user_info_map.from_id_token_payload.user_id is None:
219221
input_config.user_info_map.from_id_token_payload.user_id = "sub"
220222

@@ -226,6 +228,17 @@ def _normalize_input( # pylint: disable=no-self-use
226228
"email_verified"
227229
)
228230

231+
if input_config.user_info_map.from_user_info_api.user_id is None:
232+
input_config.user_info_map.from_user_info_api.user_id = "sub"
233+
234+
if input_config.user_info_map.from_user_info_api.email is None:
235+
input_config.user_info_map.from_user_info_api.email = "email"
236+
237+
if input_config.user_info_map.from_user_info_api.email_verified is None:
238+
input_config.user_info_map.from_user_info_api.email_verified = (
239+
"email_verified"
240+
)
241+
229242
if input_config.generate_fake_email is None:
230243

231244
async def default_generate_fake_email(

supertokens_python/recipe/thirdparty/providers/facebook.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,4 @@ def Facebook(input: ProviderInput) -> Provider: # pylint: disable=redefined-bui
7676
if input.config.user_info_map.from_user_info_api.user_id is None:
7777
input.config.user_info_map.from_user_info_api.user_id = "id"
7878

79-
if input.config.user_info_map.from_user_info_api.email is None:
80-
input.config.user_info_map.from_user_info_api.email = "email"
81-
82-
if input.config.user_info_map.from_user_info_api.email_verified is None:
83-
input.config.user_info_map.from_user_info_api.email = "verified"
84-
8579
return NewProvider(input, FacebookImpl)

supertokens_python/recipe/thirdparty/providers/gitlab.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,43 @@
1414

1515
from __future__ import annotations
1616

17-
from supertokens_python.recipe.thirdparty.provider import Provider
17+
from typing import Optional, Dict, Any
18+
19+
from supertokens_python.recipe.thirdparty.provider import (
20+
Provider,
21+
ProviderConfigForClient,
22+
)
1823
from .custom import GenericProvider, NewProvider
1924
from ..provider import Provider, ProviderInput
2025

2126

2227
class GitlabImpl(GenericProvider):
23-
pass
28+
async def get_config_for_client_type(
29+
self, client_type: Optional[str], user_context: Dict[str, Any]
30+
) -> ProviderConfigForClient:
31+
config = await super().get_config_for_client_type(client_type, user_context)
32+
33+
if config.scope is None:
34+
config.scope = ["openid", "email"]
35+
36+
if config.oidc_discovery_endpoint is None:
37+
if config.additional_config is not None and config.additional_config.get(
38+
"gitlabBaseUrl"
39+
):
40+
config.oidc_discovery_endpoint = config.additional_config[
41+
"gitlabBaseUrl"
42+
]
43+
else:
44+
config.oidc_discovery_endpoint = "https://gitlab.com"
45+
46+
return config
2447

2548

2649
def Gitlab(input: ProviderInput) -> Provider: # pylint: disable=redefined-builtin
50+
if input.config.name is None:
51+
input.config.name = "Gitlab"
52+
53+
if input.config.oidc_discovery_endpoint is None:
54+
input.config.oidc_discovery_endpoint = "https://gitlab.com"
55+
2756
return NewProvider(input, GitlabImpl)

supertokens_python/recipe/thirdparty/providers/google.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,6 @@ def Google(
5454
if input.config.user_info_map is None:
5555
input.config.user_info_map = UserInfoMap(UserFields(), UserFields())
5656

57-
if input.config.user_info_map.from_user_info_api.user_id is None:
58-
input.config.user_info_map.from_user_info_api.user_id = "id"
59-
60-
if input.config.user_info_map.from_user_info_api.email is None:
61-
input.config.user_info_map.from_user_info_api.email = "email"
62-
63-
if input.config.user_info_map.from_user_info_api.email_verified is None:
64-
input.config.user_info_map.from_user_info_api.email = "email_verified"
65-
6657
if input.config.authorization_endpoint_query_params is None:
6758
input.config.authorization_endpoint_query_params = {}
6859

supertokens_python/recipe/thirdparty/providers/linkedin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ async def get_user_info(
6464
)
6565
raw_user_info_from_provider.from_user_info_api = user_info
6666

67+
email_api_url = "https://api.linkedin.com/v2/emailAddress"
6768
email_info: Dict[str, Any] = await do_get_request(
68-
"https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))",
69+
email_api_url,
70+
query_params={"q": "members", "projection": "(elements*(handle~))"},
6971
headers=headers,
7072
)
7173

0 commit comments

Comments
 (0)