-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathtest_ubank.py
204 lines (179 loc) · 9.36 KB
/
test_ubank.py
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
27
28
29
30
31
32
33
34
35
36
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
from base64 import urlsafe_b64encode
from datetime import date
import pytest
from cryptography.hazmat.primitives import serialization
from httpx import HTTPStatusError
from ubank import (
Api,
Passkey,
TransactionsSearchBody,
UserVerifiedDevice,
__version__,
add_passkey,
int8array_to_bytes,
parse_public_key_credential_creation_options,
parse_public_key_credential_request_options,
prepare_assertion,
prepare_attestation,
)
def test_version():
assert __version__ == "2.1.0"
def test_int8array_to_bytes():
"""Tests bytes are correctly converted from Int8 to Uint8."""
assert list(int8array_to_bytes([57, 18, 31, -66, -42, -43, -119, 90])) == [
57,
18,
31,
190,
214,
213,
137,
90,
]
def test_parse_public_key_credential_creation_options():
"""Tests options values are properly converted."""
options = parse_public_key_credential_creation_options(
'{"rp":{"id":"www.ubank.com.au","name":"www.ubank.com.au"},"user":{"id":[57,18,31,-66,-42,-43,-119,90],"displayName":"a663a471-01d67-4f5-aca3-2f9ae784c84d","name":"a663a471-01d67-4f5-aca3-2f9ae784c84d"},"challenge":[-128,-115,84,-82,90,35,20,-7,96,-121,-61,56,-59,93,-128,-37,-107,-41,37,62,-14,23,-102,23,-118,-76,-111,41,-88,36,59,36],"pubKeyCredParams":[{"type":"public-key","alg":"-7"},{"type":"public-key","alg":"-37"},{"type":"public-key","alg":"-257"}],"timeout":120000,"excludeCredentials":[{"type":"public-key","id":[57,18,31,-66,-42,-43,-119,91]}],"authenticatorSelection":{"authenticatorAttachment":"platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"attestation":"none","extensions":{"credProps":true,"hmacCreateSecret":true}}'
)
assert options["publicKey"]["user"]["id"] == b"9\x12\x1f\xbe\xd6\xd5\x89Z"
assert (
options["publicKey"]["challenge"]
== b"\x80\x8dT\xaeZ#\x14\xf9`\x87\xc38\xc5]\x80\xdb\x95\xd7%>\xf2\x17\x9a\x17\x8a\xb4\x91)\xa8$;$"
)
assert {"type": "public-key", "alg": -7} in options["publicKey"]["pubKeyCredParams"]
# We captured the following challenge value from the wire sent back to ubank
# (without the trailing '='). This verifies the conversion of the challenge
# value from signed ints, to bytes and to base64 encoded bytes.
assert (
urlsafe_b64encode(options["publicKey"]["challenge"])
== b"gI1UrlojFPlgh8M4xV2A25XXJT7yF5oXirSRKagkOyQ="
)
def test_parse_public_key_credential_request_options():
"""Tests challenge value is properly converted."""
options = parse_public_key_credential_request_options(
'{"challenge":[-128,-115,84,-82,90,35,20,-7,96,-121,-61,56,-59,93,-128,-37,-107,-41,37,62,-14,23,-102,23,-118,-76,-111,41,-88,36,59,36],"timeout":120000,"rpId":"www.ubank.com.au","allowCredentials":[{"type":"public-key","id":[-50,90,62]},{"type":"public-key","id":[-29,89,87]}],"userVerification":"required"}'
)
assert (
options["publicKey"]["challenge"]
== b"\x80\x8dT\xaeZ#\x14\xf9`\x87\xc38\xc5]\x80\xdb\x95\xd7%>\xf2\x17\x9a\x17\x8a\xb4\x91)\xa8$;$"
)
def test_serialize_attestation():
"""Tests bytes are converted to JSON-serializable strings."""
attestation = {
"id": b"VYVXb72FcRIDNpt2fYxxeCg62d_XBOdVoGqfSPGfJ5Q=",
"rawId": b"U\x85Wo\xbd\x85q\x12\x036\x9bv}\x8cqx(:\xd9\xdf\xd7\x04\xe7U\xa0j\x9fH\xf1\x9f'\x94",
"response": {
"clientDataJSON": b'{"type": "webauthn.create", "challenge": "FPlgldclPvKHwzjFXYDbFw", "origin": "https://www.ubank.com.au"}',
"attestationObject": b"\xa3cfmtdnonegattStmt\xa0hauthDataX\xa4\xf3\n\x01h\xc8E\xb5\xe5\x97>Q\x0c\xf2/\x0e\xfa\x80\x02\x9d\x15\xd6U\\\xab\x85\x9b\x0f\xbb\xfd%@^A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 U\x85Wo\xbd\x85q\x12\x036\x9bv}\x8cqx(:\xd9\xdf\xd7\x04\xe7U\xa0j\x9fH\xf1\x9f'\x94\xa5\x01\x02\x03& \x01!X \xc2^\x9b\x11\x87\xc1\xb9\xa3t\xae\x0bR\xe4_\x189\xbf\xfa\xba\xc8=fy\xf6!h~7\xdb0\x8a1\"X \x93\xa7\xb3\x8d\xc4\x94\xa3\xadm\xb6\xc8_\x06\xbf]\xf6\xb6B\xae\x18^\xd3\x1e\xaaA\x00\x88\x05\x9a\xc1\x06B",
},
"type": "public-key",
}
assert prepare_attestation(attestation) == {
"id": "VYVXb72FcRIDNpt2fYxxeCg62d_XBOdVoGqfSPGfJ5Q=",
"rawId": "VYVXb72FcRIDNpt2fYxxeCg62d/XBOdVoGqfSPGfJ5Q=",
"response": {
"clientDataJSON": "eyJ0eXBlIjogIndlYmF1dGhuLmNyZWF0ZSIsICJjaGFsbGVuZ2UiOiAiRlBsZ2xkY2xQdktId3pqRlhZRGJGdyIsICJvcmlnaW4iOiAiaHR0cHM6Ly93d3cudWJhbmsuY29tLmF1In0=",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVik8woBaMhFteWXPlEM8i8O+oACnRXWVVyrhZsPu/0lQF5BAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFWFV2+9hXESAzabdn2McXgoOtnf1wTnVaBqn0jxnyeUpQECAyYgASFYIMJemxGHwbmjdK4LUuRfGDm/+rrIPWZ59iFofjfbMIoxIlggk6ezjcSUo61ttshfBr9d9rZCrhhe0x6qQQCIBZrBBkI=",
},
"type": "public-key",
}
def test_serialize_assertion():
"""Tests bytes are converted to JSON-serializable strings."""
assertion = {
"id": b"AI71FKYn9UU0GlDFWF03d7LKJu_cHdo3gJUJRao39bQ=",
"rawId": b"\x00\x8e\xf5\x14\xa6'\xf5E4\x1aP\xc5X]7w\xb2\xca&\xef\xdc\x1d\xda7\x80\x95\tE\xaa7\xf5\xb4",
"response": {
"authenticatorData": b"\xf3\n\x01h\xc8E\xb5\xe5\x97>Q\x0c\xf2/\x0e\xfa\x80\x02\x9d\x15\xd6U\\\xab\x85\x9b\x0f\xbb\xfd%@^\x01\x00\x00\x00\x01",
"clientDataJSON": b'{"type": "webauthn.get", "challenge": "gI1UrlojFPlgh8M4xV2A25XXJT7yF5oXirSRKagkOyQ", "origin": "https://www.ubank.com.au"}',
"signature": b"0E\x02!\x00\xe0\x12>f\xe3\x11\xac\x02n\x1e\x05\xc80\x97\x99L\x89\xf6H0\x0e7qo\xb0\xafft\xf6C=\xac\x02 \x0cT\x0b @Q\xf3*\xe9-.#\x88EE\r\x16\xa7S\xbd\xd0\xbd\xb7f\xcc\xd2t\x0f \xa2/\xc8",
"userHandle": b"9\x12\x1f\xbe\xd6\xd5\x89Z",
},
"type": "public-key",
}
assert prepare_assertion(assertion) == {
"id": "AI71FKYn9UU0GlDFWF03d7LKJu_cHdo3gJUJRao39bQ=",
"rawId": "AI71FKYn9UU0GlDFWF03d7LKJu/cHdo3gJUJRao39bQ=",
"response": {
"authenticatorData": "8woBaMhFteWXPlEM8i8O+oACnRXWVVyrhZsPu/0lQF4BAAAAAQ==",
"clientDataJSON": "eyJ0eXBlIjogIndlYmF1dGhuLmdldCIsICJjaGFsbGVuZ2UiOiAiZ0kxVXJsb2pGUGxnaDhNNHhWMkEyNVhYSlQ3eUY1b1hpclNSS2Fna095USIsICJvcmlnaW4iOiAiaHR0cHM6Ly93d3cudWJhbmsuY29tLmF1In0=",
"signature": "MEUCIQDgEj5m4xGsAm4eBcgwl5lMifZIMA43cW+wr2Z09kM9rAIgDFQLIEBR8yrpLS4jiEVFDRanU73QvbdmzNJ0DyCiL8g=",
"userHandle": "ORIfvtbViVo=",
},
"type": "public-key",
}
def test_passkey_serialization(tmp_path):
"""Tests passkey serialization."""
passkey = Passkey(name="")
passkey.soft_webauthn_device.create(
{
"publicKey": {
"rp": {"name": "example org", "id": "example.org"},
"user": {
"id": b"randomhandle",
"name": "username",
"displayName": "user name",
},
"challenge": b"arandomchallenge",
"pubKeyCredParams": [{"alg": -7, "type": "public-key"}],
"attestation": "none",
}
},
"https://example.org",
)
# Serialize and deserialize passkey.
with (tmp_path / "passkey.txt").open("wb") as f:
passkey.dump(f, password="")
with (tmp_path / "passkey.txt").open("rb") as f:
deserialized_passkey = Passkey.load(f, password="")
assert type(deserialized_passkey.soft_webauthn_device) is UserVerifiedDevice
def test_add_passkey_bad_username():
"""HTTPStatusErrors raised by add_passkey() should contain notes with API error text."""
try:
add_passkey("badusername", "password", "")
except HTTPStatusError as e:
assert e.__notes__
assert "Username invalid" in e.__notes__[0]
def test_api():
"""Tests declared API methods using passkey loaded from passkey.txt."""
# Skip test if hard-coded passkey file is not available.
try:
with open("passkey.txt", "rb") as f:
passkey = Passkey.load(f)
except FileNotFoundError as e:
pytest.skip(str(e))
# Authenticate to ubank with passkey.
with Api(passkey) as api:
assert (
api.client.get("accounts").json()["linkedBanks"][0]["accounts"][0][
"balance"
]["currency"]
== "AUD"
)
customer = api.get_customer_details()
assert customer.addresses[0].addressFormat == "AUS"
accounts = api.get_accounts()
assert accounts.linkedBanks[0].accounts[0].balance.currency == "AUD"
assert (
api.get_accounts_summary().linkedBanks[0].accounts[0].balance.currency
== "AUD"
)
assert api.post_accounts_transactions_search(
body=TransactionsSearchBody(
fromDate=date(2024, 1, 1), toDate=date(2025, 1, 1), limit=100
)
)
for account in accounts.linkedBanks[0].accounts:
assert api.get_account_transactions(
account_id=account.id,
bank_id=accounts.linkedBanks[0].bankId,
customerId=customer.customerId,
)
assert api.get_cards()
# Name of a device should match this passkey.
assert [
device
for device in api.get_devices(deviceUuid=passkey.device_id)
if device.deviceName == passkey.name
]
assert api.delete_device(device_id="test-2f9ae784c84d")
assert api.get_contacts()