views.py 4.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
"""
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 allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.providers.oauth2.views import (
    OAuth2Adapter,
    OAuth2CallbackView,
    OAuth2LoginView,
)

from .provider import BattleNetProvider


27 28 29 30 31 32 33 34 35 36
class Region:
    APAC = "apac"
    CN = "cn"
    EU = "eu"
    KR = "kr"
    SEA = "sea"
    TW = "tw"
    US = "us"


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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
80 81 82 83 84 85 86 87 88
    valid_regions = (
        Region.APAC,
        Region.CN,
        Region.EU,
        Region.KR,
        Region.SEA,
        Region.TW,
        Region.US,
    )
89 90 91 92

    @property
    def battlenet_region(self):
        region = self.request.GET.get("region", "").lower()
93
        if region == Region.SEA:
94
            # South-East Asia uses the same region as US everywhere
95
            return Region.US
96 97
        if region in self.valid_regions:
            return region
98
        return Region.US
99 100 101 102

    @property
    def battlenet_base_url(self):
        region = self.battlenet_region
103
        if region == Region.CN:
104 105 106 107 108 109 110 111 112 113 114 115 116
            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):
117
        return self.battlenet_base_url + "/oauth/userinfo"
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

    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)