Skip to content

Commit

Permalink
Merge branch 'release/1.13.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
nickvandegroes committed Feb 21, 2020
2 parents f487783 + 05c9688 commit cec2286
Show file tree
Hide file tree
Showing 25 changed files with 578 additions and 112 deletions.
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,24 @@

**Implemented enhancements:**

- Python SDK PSD2 [\#123](https://github.com/bunq/sdk_python/pull/123) ([angelomelonas](https://github.com/angelomelonas))
- Python SDK Refactor [\#117](https://github.com/bunq/sdk_python/pull/117) ([angelomelonas](https://github.com/angelomelonas))

**Fixed bugs:**

- Fix notification adapter and test. [\#126](https://github.com/bunq/sdk_python/pull/126) ([NickvandeGroes](https://github.com/NickvandeGroes))

**Closed issues:**

- Dependencies severely out of date \(and vulnerable: CVEs\) [\#121](https://github.com/bunq/sdk_python/issues/121)
- Typo in EXCEPTIONS.md [\#110](https://github.com/bunq/sdk_python/issues/110)

**Merged pull requests:**

- feature/fix\_typo: fix typo. [\#129](https://github.com/bunq/sdk_python/pull/129) ([angelomelonas](https://github.com/angelomelonas))
- Feature/dependency upgrades [\#128](https://github.com/bunq/sdk_python/pull/128) ([angelomelonas](https://github.com/angelomelonas))
- Add internal NotificationFilters [\#127](https://github.com/bunq/sdk_python/pull/127) ([angelomelonas](https://github.com/angelomelonas))

## [1.10.16](https://github.com/bunq/sdk_python/tree/1.10.16) (2019-06-17)
[Full Changelog](https://github.com/bunq/sdk_python/compare/1.10.2...1.10.16)

Expand Down Expand Up @@ -132,10 +148,8 @@

**Merged pull requests:**

- Regenerate code for release [\#74](https://github.com/bunq/sdk_python/pull/74) ([OGKevin](https://github.com/OGKevin))
- Regenerated code to add object types. \(bunq/sdk\_python\#53\) [\#70](https://github.com/bunq/sdk_python/pull/70) ([OGKevin](https://github.com/OGKevin))
- Bunq/sdk python\#67 add missing token qr id field [\#69](https://github.com/bunq/sdk_python/pull/69) ([OGKevin](https://github.com/OGKevin))
- Added missing id field to mastercard action. \(bunq/sdk\_python\#54\) [\#66](https://github.com/bunq/sdk_python/pull/66) ([OGKevin](https://github.com/OGKevin))
- Feature/bunq/sdk python\#59 add response id to request error [\#64](https://github.com/bunq/sdk_python/pull/64) ([OGKevin](https://github.com/OGKevin))
- Configure Zappr [\#63](https://github.com/bunq/sdk_python/pull/63) ([OGKevin](https://github.com/OGKevin))
- \(bunq/sdk\_python\#60\) improve issue and pr template [\#61](https://github.com/bunq/sdk_python/pull/61) ([OGKevin](https://github.com/OGKevin))
Expand All @@ -159,6 +173,8 @@

**Merged pull requests:**

- Regenerate code for release [\#74](https://github.com/bunq/sdk_python/pull/74) ([OGKevin](https://github.com/OGKevin))
- Added missing id field to mastercard action. \(bunq/sdk\_python\#54\) [\#66](https://github.com/bunq/sdk_python/pull/66) ([OGKevin](https://github.com/OGKevin))
- Feature/make sure headers are correctly cased bunq/sdk python\#51 [\#57](https://github.com/bunq/sdk_python/pull/57) ([OGKevin](https://github.com/OGKevin))
- Feature/improve decoder bunq/sdk python\#42 [\#56](https://github.com/bunq/sdk_python/pull/56) ([OGKevin](https://github.com/OGKevin))
- Renamed camelCase methods. \(bunq/sdk\_python\#45\) [\#48](https://github.com/bunq/sdk_python/pull/48) ([OGKevin](https://github.com/OGKevin))
Expand Down
76 changes: 41 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This SDK is in **beta**. We cannot guarantee constant availability or stability.
Thanks to your feedback we will make improvements on it.

## Installation
``pip install bunq_sdk --upgrade``
pip install bunq_sdk --upgrade

## Usage

Expand All @@ -30,19 +30,37 @@ In order to start making calls with the bunq API, you must first register your A
and create a session. In the SDKs, we group these actions and call it "creating an API context". The
context can be created by using the following code snippet:

```
apiContext = context.ApiContext(ENVIRONMENT_TYPE, API_KEY,
DEVICE_DESCRIPTION);
apiContext.save(API_CONTEXT_FILE_PATH)
context.BunqContext.loadApiContext(apiContext)
```

This code snippet, except for `context.BunqContext.loadApiContext(apiContext)` should be called once per API key.
apiContext = ApiContext.create(ENVIRONMENT_TYPE, API_KEY, DEVICE_DESCRIPTION)
apiContext.save(API_CONTEXT_FILE_PATH)


**Please note**: initialising your application is a heavy task and it is recommended to do it only once per device.

apiContext = ApiContext.restore(self.API_CONTEXT_FILE_PATH)
BunqContext.loadApiContext(apiContext)

After saving the context, you can restore it at any time:

#### Example

See [`tinker/setup_context`](https://github.com/bunq/tinker_python/blob/2182b8be276fda921657ad22cfe0b8b48a585ccf/tinker/libs/bunq_lib.py#L44-L59)

#### PSD2
It is possible to create an ApiContext as PSD2 Service Provider. Although this might seem a complex task, we wrote some
helper implementations to get you started. You need to create a certificate and private key to get you started.
Our sandbox environment currently accepts all certificates, if these criteria are met:

- Up to 64 characters
- PISP and/or AISP used in the end.

Make sure you have your unique eIDAS certificate number and certificates ready when you want to perform these tasks on
our production environment.

Creating a PSD2 context is very easy:

apiContext = ApiContext.create_for_psd2(ENVIRONMENT_TYPE, CERTIFICATE, PRIVATE_KEY, CERTIFICATE_CHAIN, DEVICE_DESCRIPTION)

#### Safety considerations
The file storing the context details (i.e. `bunq.conf`) is a key to your account. Anyone having
access to it is able to perform any Public API actions with your account. Therefore, we recommend
Expand All @@ -62,14 +80,11 @@ Creating objects through the API requires an `ApiContext`, a `requestMap` and id
dependencies (such as User ID required for accessing a Monetary Account). Optionally, custom headers
can be passed to requests.


```
payment_id = endpoint.Payment.create(
amount=Amount(amount_string, self._CURRENCY_EURL),
counterparty_alias=Pointer(self._POINTER_TYPE_EMAIL, recipient),
description=description
payment_id = endpoint.Payment.create(
amount=Amount(amount_string, self._CURRENCY_EURL),
counterparty_alias=Pointer(self._POINTER_TYPE_EMAIL, recipient),
description=description
)
```

##### Example
See [`tinker/make_payment`](https://github.com/bunq/tinker_python/blob/2182b8be276fda921657ad22cfe0b8b48a585ccf/tinker/libs/bunq_lib.py#L140-L151)
Expand All @@ -81,11 +96,9 @@ UUID) Optionally, custom headers can be passed to requests.

This type of calls always returns a model.

```
monetary_account = generated.MonetaryAccountBank.get(
_MONETARY_ACCOUNT_ITEM_ID
)
```
monetary_account = generated.MonetaryAccountBank.get(
_MONETARY_ACCOUNT_ITEM_ID
)

##### Example
See [`tinker/list_all_payment`](https://github.com/bunq/tinker_python/blob/2182b8be276fda921657ad22cfe0b8b48a585ccf/tinker/libs/bunq_lib.py#L85-L103)
Expand All @@ -94,12 +107,10 @@ See [`tinker/list_all_payment`](https://github.com/bunq/tinker_python/blob/2182b
Updating objects through the API goes the same way as creating objects, except that also the object to update identifier
(ID or UUID) is needed.

```
endpoint.Card.update(
card_id=int(card_id),
monetary_account_current_id=int(account_id)
)
```
endpoint.Card.update(
card_id=int(card_id),
monetary_account_current_id=int(account_id)
)

##### Example
See [`tinker/update_card`](https://github.com/bunq/tinker_python/blob/2182b8be276fda921657ad22cfe0b8b48a585ccf/tinker/libs/bunq_lib.py#L167-L174)
Expand All @@ -109,20 +120,15 @@ Deleting objects through the API requires an `ApiContext`, identifiers of all de
accessing a Monetary Account), and the identifier of the object to delete (ID or UUID) Optionally, custom headers can be
passed to requests.

```
Session.delete(self._SESSION_ID)
```
Session.delete(self._SESSION_ID)

##### Example


#### Listing objects
Listing objects through the API requires an `ApiContext` and identifiers of all dependencies (such as User ID required
for accessing a Monetary Account). Optionally, custom headers can be passed to requests.

```
users = generated.User.list(api_context)
```
users = endpoint.User.list(api_context)

##### Example
See [`UserListExample.py`](./examples/user_list_example.py)
Expand All @@ -133,8 +139,8 @@ To get an indication on how the SDK works you can use the python tinker which is
## Running Tests

Information regarding the test cases can be found in the [README.md](./tests/README.md)
located in [test](/tests)
located in [test](/tests).

## Exceptions
The SDK can raise multiple exceptions. For an overview of these exceptions please
take a look at [EXCEPTIONS.md](./bunq/sdk/exception/EXCEPTIONS.md)
take a look at [EXCEPTIONS.md](./bunq/sdk/exception/EXCEPTIONS.md).
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.13.0
1.13.1
110 changes: 86 additions & 24 deletions bunq/sdk/context/api_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from bunq.sdk.context.session_context import SessionContext
from bunq.sdk.exception.bunq_exception import BunqException
from bunq.sdk.json import converter
from bunq.sdk.model.core.payment_service_provider_credential_internal import PaymentServiceProviderCredentialInternal
from bunq.sdk.model.generated import endpoint
from bunq.sdk.model.generated.endpoint import UserCredentialPasswordIp, UserPaymentServiceProvider
from bunq.sdk.security import security

if typing.TYPE_CHECKING:
Expand All @@ -21,9 +23,9 @@
class ApiContext:
"""
:type _environment_type: ApiEnvironmentType
:type _api_key: str
:type _session_context: SessionContext
:type _installation_context: InstallationContext
:type _api_key: str|None
:type _session_context: SessionContext|None
:type _installation_context: InstallationContext|None
:type _proxy_url: str|None
"""

Expand All @@ -42,28 +44,56 @@ class ApiContext:

def __init__(self,
environment_type: ApiEnvironmentType,
api_key: str,
device_description: str,
permitted_ips: List[str] = None,
proxy_url: List[str] = None) -> None:
if permitted_ips is None:
permitted_ips = []

self._environment_type = environment_type
self._api_key = api_key
self._proxy_url = proxy_url
self._api_key = None
self._installation_context = None
self._session_context = None
self._proxy_url = proxy_url
self._initialize(device_description, permitted_ips)

def _initialize(self,
device_description: str,
permitted_ips: List[str]) -> None:
self._initialize_installation()
self._register_device(device_description, permitted_ips)
self._initialize_session()
@classmethod
def create(cls,
environment_type: ApiEnvironmentType,
api_key: str,
description: str,
all_permitted_ip: List[str] = None,
proxy_url: List[str] = None) -> ApiContext:
api_context = cls(environment_type, proxy_url)

api_context._api_key = api_key

api_context.__initialize_installation()
api_context.__register_device(description, all_permitted_ip)
api_context.__initialize_session()

return api_context

@classmethod
def create_for_psd2(cls,
environment_type: ApiEnvironmentType,
certificate: str,
private_key: str,
all_chain_certificate: List[str],
description: str,
all_permitted_ip: List[str] = None,
proxy_url: List[str] = None) -> ApiContext:
api_context = cls(environment_type, proxy_url)

api_context.__initialize_installation()

service_provider_credential = api_context.__initialize_psd2_credential(
certificate,
private_key,
all_chain_certificate)

api_context._api_key = service_provider_credential.token_value

api_context.__register_device(description, all_permitted_ip)
api_context.__initialize_session_for_psd2(service_provider_credential)

return api_context

def _initialize_installation(self) -> None:
def __initialize_installation(self) -> None:
from bunq.sdk.model.core.installation import Installation

private_key_client = security.generate_rsa_private_key()
Expand All @@ -83,9 +113,28 @@ def _initialize_installation(self) -> None:
public_key_server
)

def _register_device(self,
device_description: str,
permitted_ips: List[str]) -> None:
def __initialize_psd2_credential(self,
certificate: str,
private_key: str,
all_chain_certificate: List[str], ) -> UserCredentialPasswordIp:
session_token = self.installation_context.token
client_key_pair = self.installation_context.private_key_client

string_to_sign = security.public_key_to_string(client_key_pair.publickey()) + "\n" + session_token
encoded_signature = security.generate_signature(string_to_sign, security.rsa_key_from_string(private_key))

payment_response_provider = PaymentServiceProviderCredentialInternal.create_with_api_context(
certificate,
security.get_certificate_chain_string(all_chain_certificate),
encoded_signature,
self
)

return payment_response_provider

def __register_device(self,
device_description: str,
permitted_ips: List[str]) -> None:
from bunq.sdk.model.core.device_server_internal import DeviceServerInternal

DeviceServerInternal.create(
Expand All @@ -95,7 +144,7 @@ def _register_device(self,
api_context=self
)

def _initialize_session(self) -> None:
def __initialize_session(self) -> None:
from bunq.sdk.model.core.session_server import SessionServer

session_server = SessionServer.create(self).value
Expand All @@ -105,6 +154,17 @@ def _initialize_session(self) -> None:

self._session_context = SessionContext(token, expiry_time, user_id)

def __initialize_session_for_psd2(self, user_payment_service_provider: UserPaymentServiceProvider) -> None:
from bunq.sdk.model.core.session_server import SessionServer

session_server = SessionServer.create(self).value

token = session_server.token.token
expiry_time = self._get_expiry_timestamp(session_server)
user_id = session_server.get_referenced_user().id_

self._session_context = SessionContext(token, expiry_time, user_id)

@classmethod
def _get_expiry_timestamp(cls, session_server: SessionServer) -> datetime.datetime:
timeout_seconds = cls._get_session_timeout_seconds(session_server)
Expand All @@ -118,6 +178,8 @@ def _get_session_timeout_seconds(cls, session_server: SessionServer) -> int:
return session_server.user_company.session_timeout
elif session_server.user_person is not None:
return session_server.user_person.session_timeout
elif session_server.user_payment_service_provider is not None:
return session_server.user_payment_service_provider.session_timeout
elif session_server.user_api_key is not None:
return session_server \
.user_api_key \
Expand Down Expand Up @@ -159,7 +221,7 @@ def reset_session(self) -> None:
"""

self._drop_session_context()
self._initialize_session()
self.__initialize_session()

def _drop_session_context(self) -> None:
self._session_context = None
Expand Down
11 changes: 11 additions & 0 deletions bunq/sdk/context/user_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def __init__(self, user_id: int) -> None:
self._user_person = None
self._user_company = None
self._user_api_key = None
self._user_payment_service_provider = None
self._primary_monetary_account = None

self._set_user(self.__get_user_object())
Expand All @@ -32,11 +33,17 @@ def _set_user(self, user: BunqModel) -> None:
elif isinstance(user, endpoint.UserApiKey):
self._user_api_key = user

elif isinstance(user, endpoint.UserPaymentServiceProvider):
self._user_payment_service_provider = user

else:
raise BunqException(
self._ERROR_UNEXPECTED_USER_INSTANCE.format(user.__class__))

def init_main_monetary_account(self) -> None:
if self._user_payment_service_provider is not None:
return

all_monetary_account = endpoint.MonetaryAccountBank.list().value

for account in all_monetary_account:
Expand Down Expand Up @@ -73,6 +80,10 @@ def is_all_user_type_set(self) -> bool:

def refresh_user_context(self) -> None:
self._set_user(self.__get_user_object())

if self._user_payment_service_provider is not None:
return

self.init_main_monetary_account()

@property
Expand Down
Loading

0 comments on commit cec2286

Please sign in to comment.