Skip to content

Commit f0b5999

Browse files
committed
adds bitbucket provider
1 parent 772f24f commit f0b5999

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

supertokens_python/recipe/thirdparty/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
Github = providers.Github
3030
Google = providers.Google
3131
GoogleWorkspaces = providers.GoogleWorkspaces
32+
Bitbucket = providers.Bitbucket
3233
exceptions = ex
3334

3435
if TYPE_CHECKING:

supertokens_python/recipe/thirdparty/providers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
from .github import Github # type: ignore
1818
from .google import Google # type: ignore
1919
from .google_workspaces import GoogleWorkspaces # type: ignore
20+
from .bitbucket import Bitbucket # type: ignore
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved.
2+
#
3+
# This software is licensed under the Apache License, Version 2.0 (the
4+
# "License") as published by the Apache Software Foundation.
5+
#
6+
# You may not use this file except in compliance with the License. You may
7+
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from __future__ import annotations
16+
17+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union
18+
19+
from httpx import AsyncClient
20+
from supertokens_python.recipe.thirdparty.provider import Provider
21+
from supertokens_python.recipe.thirdparty.types import (
22+
AccessTokenAPI,
23+
AuthorisationRedirectAPI,
24+
UserInfo,
25+
UserInfoEmail,
26+
)
27+
28+
if TYPE_CHECKING:
29+
from supertokens_python.framework.request import BaseRequest
30+
31+
32+
class Bitbucket(Provider):
33+
def __init__(
34+
self,
35+
client_id: str,
36+
client_secret: str,
37+
scope: Union[None, List[str]] = None,
38+
authorisation_redirect: Union[
39+
None, Dict[str, Union[str, Callable[[BaseRequest], str]]]
40+
] = None,
41+
is_default: bool = False,
42+
):
43+
super().__init__("bitbucket", is_default)
44+
self.client_id = client_id
45+
self.client_secret = client_secret
46+
self.scopes = ["account", "email"] if scope is None else list(set(scope))
47+
self.access_token_api_url = "https://bitbucket.org/site/oauth2/access_token"
48+
self.authorisation_redirect_url = "https://bitbucket.org/site/oauth2/authorize"
49+
self.authorisation_redirect_params = {}
50+
if authorisation_redirect is not None:
51+
self.authorisation_redirect_params = authorisation_redirect
52+
53+
async def get_profile_info(
54+
self, auth_code_response: Dict[str, Any], user_context: Dict[str, Any]
55+
) -> UserInfo:
56+
access_token: str = auth_code_response["access_token"]
57+
headers = {"Authorization": f"Bearer {access_token}"}
58+
async with AsyncClient() as client:
59+
response = await client.get( # type: ignore
60+
url="https://api.bitbucket.org/2.0/user",
61+
headers=headers,
62+
)
63+
user_info = response.json()
64+
user_id = user_info["uuid"]
65+
email_res = await client.get( # type: ignore
66+
url="https://api.bitbucket.org/2.0/user/emails",
67+
headers=headers,
68+
)
69+
email_data = email_res.json()
70+
email = None
71+
is_verified = False
72+
for email_info in email_data["values"]:
73+
if email_info.get("is_primary"):
74+
email = email_info["email"]
75+
is_verified = email_info["is_confirmed"]
76+
break
77+
78+
if email is None:
79+
return UserInfo(user_id)
80+
return UserInfo(user_id, UserInfoEmail(email, is_verified))
81+
82+
def get_authorisation_redirect_api_info(
83+
self, user_context: Dict[str, Any]
84+
) -> AuthorisationRedirectAPI:
85+
params = {
86+
"scope": " ".join(self.scopes),
87+
"response_type": "code",
88+
"client_id": self.client_id,
89+
"access_type": "offline",
90+
"include_granted_scopes": "true",
91+
**self.authorisation_redirect_params,
92+
}
93+
return AuthorisationRedirectAPI(self.authorisation_redirect_url, params)
94+
95+
def get_access_token_api_info(
96+
self,
97+
redirect_uri: str,
98+
auth_code_from_request: str,
99+
user_context: Dict[str, Any],
100+
) -> AccessTokenAPI:
101+
params = {
102+
"client_id": self.client_id,
103+
"client_secret": self.client_secret,
104+
"grant_type": "authorization_code",
105+
"code": auth_code_from_request,
106+
"redirect_uri": redirect_uri,
107+
}
108+
return AccessTokenAPI(self.access_token_api_url, params)
109+
110+
def get_redirect_uri(self, user_context: Dict[str, Any]) -> Union[None, str]:
111+
return None
112+
113+
def get_client_id(self, user_context: Dict[str, Any]) -> str:
114+
return self.client_id

supertokens_python/recipe/thirdpartyemailpassword/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
Github = thirdparty.Github
3636
Google = thirdparty.Google
3737
GoogleWorkspaces = thirdparty.GoogleWorkspaces
38+
Bitbucket = thirdparty.Bitbucket
3839
SMTPService = emaildelivery_services.SMTPService
3940

4041
if TYPE_CHECKING:

supertokens_python/recipe/thirdpartypasswordless/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
Github = thirdparty.Github
3939
Google = thirdparty.Google
4040
GoogleWorkspaces = thirdparty.GoogleWorkspaces
41+
Bitbucket = thirdparty.Bitbucket
4142
ContactPhoneOnlyConfig = passwordless.ContactPhoneOnlyConfig
4243
ContactEmailOnlyConfig = passwordless.ContactEmailOnlyConfig
4344
ContactEmailOrPhoneConfig = passwordless.ContactEmailOrPhoneConfig

0 commit comments

Comments
 (0)