Skip to content

Commit 54ee89f

Browse files
committed
feature/sdk_python#136: Added OauthAccessToken and OauthGrantType.
1 parent 2f7c372 commit 54ee89f

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

bunq/sdk/http/anonymous_api_client.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from typing import Dict
2+
3+
import requests
4+
5+
from bunq.sdk.context.api_context import ApiContext
6+
from bunq.sdk.http.api_client import ApiClient
7+
from bunq.sdk.http.bunq_response_raw import BunqResponseRaw
8+
from bunq.sdk.security import security
9+
10+
11+
class AnonymousApiClient(ApiClient):
12+
13+
def __init__(self, api_context: ApiContext) -> None:
14+
super().__init__(api_context)
15+
16+
def post(self,
17+
uri_relative: str,
18+
request_bytes: bytes,
19+
custom_headers: Dict[str, str]) -> BunqResponseRaw:
20+
return self._request(
21+
self.METHOD_POST,
22+
uri_relative,
23+
request_bytes,
24+
{},
25+
custom_headers
26+
)
27+
28+
def _request(self,
29+
method: str,
30+
uri_relative: str,
31+
request_bytes: bytes,
32+
params: Dict[str, str],
33+
custom_headers: Dict[str, str]) -> BunqResponseRaw:
34+
from bunq.sdk.context.bunq_context import BunqContext
35+
36+
uri_relative_with_params = self._append_params_to_uri(uri_relative, params)
37+
if uri_relative not in self._URIS_NOT_REQUIRING_ACTIVE_SESSION:
38+
if self._api_context.ensure_session_active():
39+
BunqContext.update_api_context(self._api_context)
40+
41+
all_headers = self._get_all_headers(
42+
request_bytes,
43+
custom_headers
44+
)
45+
46+
response = requests.request(
47+
method,
48+
uri_relative_with_params,
49+
data=request_bytes,
50+
headers=all_headers,
51+
proxies={self.FIELD_PROXY_HTTPS: self._api_context.proxy_url},
52+
)
53+
54+
self._assert_response_success(response)
55+
56+
if self._api_context.installation_context is not None:
57+
security.validate_response(
58+
self._api_context.installation_context.public_key_server,
59+
response.status_code,
60+
response.content,
61+
response.headers
62+
)
63+
64+
return BunqResponseRaw(response.content, response.headers)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional, Type
4+
5+
from bunq import ApiEnvironmentType
6+
from bunq.sdk.context.bunq_context import BunqContext
7+
from bunq.sdk.exception.bunq_exception import BunqException
8+
from bunq.sdk.http.anonymous_api_client import AnonymousApiClient
9+
from bunq.sdk.http.bunq_response import BunqResponse
10+
from bunq.sdk.http.bunq_response_raw import BunqResponseRaw
11+
from bunq.sdk.http.http_util import HttpUtil
12+
from bunq.sdk.json import converter
13+
from bunq.sdk.model.core.bunq_model import BunqModel
14+
from bunq.sdk.model.core.oauth_grant_type import OauthGrantType
15+
from bunq.sdk.model.generated.endpoint import OauthClient
16+
from bunq.sdk.util.type_alias import T
17+
18+
19+
class OauthAccessToken(BunqModel):
20+
# Field constants.
21+
FIELD_GRANT_TYPE = "grant_type"
22+
FIELD_CODE = "code"
23+
FIELD_REDIRECT_URI = "redirect_uri"
24+
FIELD_CLIENT_ID = "client_id"
25+
FIELD_CLIENT_SECRET = "client_secret"
26+
27+
# Token constants.
28+
TOKEN_URI_FORMAT_SANDBOX = "https://api-oauth.sandbox.bunq.com/v1/token?%s"
29+
TOKEN_URI_FORMAT_PRODUCTION = "https://api.oauth.bunq.com/v1/token?%s"
30+
31+
# Error constants.
32+
ERROR_ENVIRONMENT_TYPE_NOT_SUPPORTED = "You are trying to use an unsupported environment type."
33+
34+
def __init__(self, token: str, token_type: str, state: str = None) -> None:
35+
self._token = token
36+
self._token_type = token_type
37+
self._state = state
38+
39+
@property
40+
def token(self) -> str:
41+
return self._token
42+
43+
@property
44+
def token_type(self) -> str:
45+
return self._token_type
46+
47+
@property
48+
def state(self) -> Optional[str]:
49+
return self._state
50+
51+
@classmethod
52+
def create(cls,
53+
grant_type: OauthGrantType,
54+
oauth_code: str,
55+
redirect_uri: str,
56+
client: OauthClient) -> OauthAccessToken:
57+
api_client = AnonymousApiClient(BunqContext.api_context())
58+
response_raw = api_client.post(
59+
cls.create_token_uri(grant_type.value, oauth_code, redirect_uri, client),
60+
bytearray(),
61+
{}
62+
)
63+
64+
return cls.from_json(OauthAccessToken, response_raw).value
65+
66+
@classmethod
67+
def create_token_uri(cls, grant_type: str, auth_code: str, redirect_uri: str, client: OauthClient) -> str:
68+
all_token_parameter = {
69+
cls.FIELD_GRANT_TYPE: grant_type,
70+
cls.FIELD_CODE: auth_code,
71+
cls.FIELD_REDIRECT_URI: redirect_uri,
72+
cls.FIELD_CLIENT_ID: client.id_,
73+
cls.FIELD_CLIENT_SECRET: client.secret,
74+
}
75+
76+
return cls.determine_auth_uri_format().format(HttpUtil.create_query_string(all_token_parameter))
77+
78+
def is_all_field_none(self) -> bool:
79+
if self._token is not None:
80+
return False
81+
elif self._token_type is not None:
82+
return False
83+
elif self._state is not None:
84+
return False
85+
86+
return True
87+
88+
@classmethod
89+
def from_json(cls, class_of_object: Type[T], response_raw: BunqResponseRaw):
90+
response_item_object = converter.deserialize(class_of_object, response_raw)
91+
response_value = converter.json_to_class(class_of_object, response_item_object)
92+
93+
return BunqResponse(response_value, response_raw.headers)
94+
95+
@classmethod
96+
def determine_auth_uri_format(cls) -> str:
97+
environment_type = BunqContext.api_context().environment_type
98+
99+
if ApiEnvironmentType.PRODUCTION == environment_type:
100+
return cls.TOKEN_URI_FORMAT_PRODUCTION
101+
102+
if ApiEnvironmentType.SANDBOX == environment_type:
103+
return cls.TOKEN_URI_FORMAT_SANDBOX
104+
105+
raise BunqException(cls.ERROR_ENVIRONMENT_TYPE_NOT_SUPPORTED)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import aenum
2+
3+
4+
class OauthGrantType(aenum.AutoNumberEnum):
5+
"""
6+
:type AUTHORIZATION_CODE: str
7+
:type grant_type: str
8+
"""
9+
10+
AUTHORIZATION_CODE = 'authorization_code'
11+
12+
def __init__(self, grant_type: str) -> None:
13+
self._grant_type = grant_type
14+
15+
@property
16+
def grant_type(self) -> str:
17+
return self.grant_type

0 commit comments

Comments
 (0)