This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
"""
OAuth2 Adapter for Battle.net
Resources:
* Battle.net OAuth2 documentation:
https://dev.battle.net/docs/read/oauth
* Battle.net API documentation:
https://dev.battle.net/io-docs
* Original announcement:
https://us.battle.net/en/forum/topic/13979297799
* The Battle.net API forum:
https://us.battle.net/en/forum/15051532/
"""
import requests
from django.conf import settings
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from .provider import BattleNetProvider
class Region:
APAC = "apac"
CN = "cn"
EU = "eu"
KR = "kr"
SEA = "sea"
TW = "tw"
US = "us"
def _check_errors(response):
try:
data = response.json()
except ValueError: # JSONDecodeError on py3
raise OAuth2Error("Invalid JSON from Battle.net API: %r" % (response.text))
if response.status_code >= 400 or "error" in data:
# For errors, we expect the following format:
# {"error": "error_name", "error_description": "Oops!"}
# For example, if the token is not valid, we will get:
# {
# "error": "invalid_token",
# "error_description": "Invalid access token: abcdef123456"
# }
# For the profile API, this may also look like the following:
# {"code": 403, "type": "Forbidden", "detail": "Account Inactive"}
error = data.get("error", "") or data.get("type", "")
desc = data.get("error_description", "") or data.get("detail", "")
raise OAuth2Error("Battle.net error: %s (%s)" % (error, desc))
# The expected output from the API follows this format:
# {"id": 12345, "battletag": "Example#12345"}
# The battletag is optional.
if "id" not in data:
# If the id is not present, the output is not usable (no UID)
raise OAuth2Error("Invalid data from Battle.net API: %r" % (data))
return data
class BattleNetOAuth2Adapter(OAuth2Adapter):
"""
OAuth2 adapter for Battle.net
https://dev.battle.net/docs/read/oauth
Region is set to us by default, but can be overridden with the
`region` GET parameter when performing a login.
Can be any of eu, us, kr, sea, tw or cn
"""
provider_id = BattleNetProvider.id
valid_regions = (
Region.APAC,
Region.CN,
Region.EU,
Region.KR,
Region.SEA,
Region.TW,
Region.US,
)
@property
def battlenet_region(self):
# Check by URI query parameter first.
region = self.request.GET.get("region", "").lower()
if region == Region.SEA:
# South-East Asia uses the same region as US everywhere
return Region.US
if region in self.valid_regions:
return region
# Second, check the provider settings.
region = (
getattr(settings, "SOCIALACCOUNT_PROVIDERS", {})
.get("battlenet", {})
.get("REGION", "us")
)
if region in self.valid_regions:
return region
return Region.US
@property
def battlenet_base_url(self):
region = self.battlenet_region
if region == Region.CN:
return "https://www.battlenet.com.cn"
return "https://%s.battle.net" % (region)
@property
def access_token_url(self):
return self.battlenet_base_url + "/oauth/token"
@property
def authorize_url(self):
return self.battlenet_base_url + "/oauth/authorize"
@property
def profile_url(self):
return self.battlenet_base_url + "/oauth/userinfo"
def complete_login(self, request, app, token, **kwargs):
params = {"access_token": token.token}
response = requests.get(self.profile_url, params=params)
data = _check_errors(response)
# Add the region to the data so that we can have it in `extra_data`.
data["region"] = self.battlenet_region
return self.get_provider().sociallogin_from_response(request, data)
def get_callback_url(self, request, app):
r = super(BattleNetOAuth2Adapter, self).get_callback_url(request, app)
region = request.GET.get("region", "").lower()
# Pass the region down to the callback URL if we specified it
if region and region in self.valid_regions:
r += "?region=%s" % (region)
return r
oauth2_login = OAuth2LoginView.adapter_view(BattleNetOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(BattleNetOAuth2Adapter)