Skip to content

Commit f26323f

Browse files
justinnoutJustin Herrera
andauthored
Add granularity for DID Token Errors (#50)
* Add granular error types for DID token * Use granular error types * Add tests * Use DIDTokenInvalid over DIDTokenError * Update changelog * remove temporal messaging Co-authored-by: Justin Herrera <[email protected]>
1 parent e37b143 commit f26323f

File tree

7 files changed

+61
-26
lines changed

7 files changed

+61
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212

1313
- <PR-#ISSUE> ...
1414

15+
## `0.2.0` - 1/04/2023
16+
17+
#### Added
18+
19+
- PR-#50: Split up DIDTokenError into DIDTokenExpired, DIDTokenMalformed, and DIDTokenInvalid.
20+
1521
## `0.1.0` - 11/30/2022
1622

1723
#### Added

magic_admin/error.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ def to_dict(self):
1717
return {'message': str(self)}
1818

1919

20-
class DIDTokenError(MagicError):
20+
class DIDTokenInvalid(MagicError):
21+
pass
22+
23+
24+
class DIDTokenMalformed(MagicError):
25+
pass
26+
27+
28+
class DIDTokenExpired(MagicError):
2129
pass
2230

2331

magic_admin/resources/token.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from eth_account.messages import defunct_hash_message
55
from web3.auto import w3
66

7-
from magic_admin.error import DIDTokenError
7+
from magic_admin.error import DIDTokenExpired
8+
from magic_admin.error import DIDTokenInvalid
9+
from magic_admin.error import DIDTokenMalformed
810
from magic_admin.resources.base import ResourceComponent
911
from magic_admin.utils.did_token import parse_public_address_from_issuer
1012
from magic_admin.utils.time import apply_did_token_nbf_grace_period
@@ -42,7 +44,7 @@ def _check_required_fields(cls, claim):
4244
missing_fields.append(field)
4345

4446
if missing_fields:
45-
raise DIDTokenError(
47+
raise DIDTokenMalformed(
4648
message='DID token is missing required field(s): {}'.format(
4749
sorted(missing_fields),
4850
),
@@ -55,7 +57,7 @@ def decode(cls, did_token):
5557
did_token (base64.str): Base64 encoded string.
5658
5759
Raises:
58-
DIDTokenError: If token format is invalid.
60+
DIDTokenMalformed: If token format is invalid.
5961
6062
Returns:
6163
proof (str): A signed message.
@@ -66,7 +68,7 @@ def decode(cls, did_token):
6668
base64.urlsafe_b64decode(did_token).decode('utf-8'),
6769
)
6870
except Exception as e:
69-
raise DIDTokenError(
71+
raise DIDTokenMalformed(
7072
message='DID token is malformed. It has to be a based64 encoded '
7173
'JSON serialized string. {err} ({msg}).'.format(
7274
err=e.__class__.__name__,
@@ -75,7 +77,7 @@ def decode(cls, did_token):
7577
)
7678

7779
if len(decoded_did_token) != EXPECTED_DID_TOKEN_CONTENT_LENGTH:
78-
raise DIDTokenError(
80+
raise DIDTokenMalformed(
7981
message='DID token is malformed. It has to have two parts '
8082
'[proof, claim].',
8183
)
@@ -85,7 +87,7 @@ def decode(cls, did_token):
8587
try:
8688
claim = json.loads(decoded_did_token[1])
8789
except Exception as e:
88-
raise DIDTokenError(
90+
raise DIDTokenMalformed(
8991
message='DID token is malformed. Given claim should be a JSON '
9092
'serialized string. {err} ({msg}).'.format(
9193
err=e.__class__.__name__,
@@ -130,12 +132,20 @@ def validate(cls, did_token):
130132
did_token (base64.str): Base64 encoded string.
131133
132134
Raises:
133-
DIDTokenError: If DID token fails the validation.
135+
DIDTokenInvalid: If DID token fails the validation.
136+
DIDTokenExpired: If DID token has expired.
134137
135138
Returns:
136139
None.
137140
"""
138141
proof, claim = cls.decode(did_token)
142+
143+
if claim['ext'] is None:
144+
raise DIDTokenInvalid(
145+
message='Please check the "ext" field and regenerate a new token '
146+
'with a suitable value.',
147+
)
148+
139149
recovered_address = w3.eth.account.recoverHash(
140150
defunct_hash_message(
141151
text=json.dumps(claim, separators=(',', ':')),
@@ -144,20 +154,20 @@ def validate(cls, did_token):
144154
)
145155

146156
if recovered_address != cls.get_public_address(did_token):
147-
raise DIDTokenError(
157+
raise DIDTokenInvalid(
148158
message='Signature mismatch between "proof" and "claim". Please '
149159
'generate a new token with an intended issuer.',
150160
)
151161

152162
current_time_in_s = epoch_time_now()
153163

154164
if current_time_in_s > claim['ext']:
155-
raise DIDTokenError(
165+
raise DIDTokenExpired(
156166
message='Given DID token has expired. Please generate a new one.',
157167
)
158168

159169
if current_time_in_s < apply_did_token_nbf_grace_period(claim['nbf']):
160-
raise DIDTokenError(
170+
raise DIDTokenInvalid(
161171
message='Given DID token cannot be used at this time. Please '
162172
'check the "nbf" field and regenerate a new token with a suitable '
163173
'value.',

magic_admin/utils/did_token.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from magic_admin.error import DIDTokenError
1+
from magic_admin.error import DIDTokenMalformed
22

33

44
def parse_public_address_from_issuer(issuer):
@@ -14,7 +14,7 @@ def parse_public_address_from_issuer(issuer):
1414
try:
1515
return issuer.split(':')[2]
1616
except IndexError:
17-
raise DIDTokenError(
17+
raise DIDTokenMalformed(
1818
'Given issuer ({}) is malformed. Please make sure it follows the '
1919
'`did:method-name:method-specific-id` format.'.format(issuer),
2020
)

tests/unit/error_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from magic_admin.error import APIError
33
from magic_admin.error import AuthenticationError
44
from magic_admin.error import BadRequestError
5-
from magic_admin.error import DIDTokenError
5+
from magic_admin.error import DIDTokenInvalid
66
from magic_admin.error import ForbiddenError
77
from magic_admin.error import MagicError
88
from magic_admin.error import RateLimitingError
@@ -34,9 +34,9 @@ class TestMagicError(MagicErrorBase):
3434
error_class = MagicError
3535

3636

37-
class TestDIDTokenError(MagicErrorBase):
37+
class TestDIDTokenInvalid(MagicErrorBase):
3838

39-
error_class = DIDTokenError
39+
error_class = DIDTokenInvalid
4040

4141

4242
class TestAPIConnectionError(MagicErrorBase):

tests/unit/resources/token_test.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import pytest
66

7-
from magic_admin.error import DIDTokenError
7+
from magic_admin.error import DIDTokenExpired
8+
from magic_admin.error import DIDTokenInvalid
9+
from magic_admin.error import DIDTokenMalformed
810
from magic_admin.resources.token import Token
911

1012

@@ -24,7 +26,7 @@ def test_required_fields(self):
2426
) == frozenset()
2527

2628
def test_check_required_fields_raises_error(self):
27-
with pytest.raises(DIDTokenError) as e:
29+
with pytest.raises(DIDTokenMalformed) as e:
2830
Token._check_required_fields(
2931
self._generate_claim(fields=['nbf', 'sub', 'aud', 'tid', 'iat']),
3032
)
@@ -80,7 +82,7 @@ def setup_mocks(self):
8082
def test_decode_raises_error_if_did_token_is_malformed(self, setup_mocks):
8183
setup_mocks.urlsafe_b64decode.side_effect = Exception()
8284

83-
with pytest.raises(DIDTokenError) as e:
85+
with pytest.raises(DIDTokenMalformed) as e:
8486
Token.decode(self.did_token)
8587

8688
setup_mocks.urlsafe_b64decode.assert_called_once_with(self.did_token)
@@ -90,7 +92,7 @@ def test_decode_raises_error_if_did_token_is_malformed(self, setup_mocks):
9092
def test_decode_raises_error_if_did_token_has_missing_parts(self, setup_mocks):
9193
setup_mocks.json_loads.return_value = ('miss one part')
9294

93-
with pytest.raises(DIDTokenError) as e:
95+
with pytest.raises(DIDTokenMalformed) as e:
9496
Token.decode(self.did_token)
9597

9698
setup_mocks.urlsafe_b64decode.assert_called_once_with(self.did_token)
@@ -101,7 +103,7 @@ def test_decode_raises_error_if_did_token_has_missing_parts(self, setup_mocks):
101103
'[proof, claim].'
102104

103105
def test_decode_raises_error_if_claim_is_not_json_serializable(self, setup_mocks):
104-
with pytest.raises(DIDTokenError) as e:
106+
with pytest.raises(DIDTokenMalformed) as e:
105107
setup_mocks.json_loads.side_effect = [
106108
('proof_in_str', 'claim_in_str'), # Succeeds the first time.
107109
Exception(), # Fails the second time.
@@ -228,7 +230,7 @@ def _assert_validate_funcs_called(
228230
def test_validate_raises_error_if_signature_mismatch(self, setup_mocks):
229231
setup_mocks.get_public_address.return_value = 'random_public_address'
230232

231-
with pytest.raises(DIDTokenError) as e:
233+
with pytest.raises(DIDTokenInvalid) as e:
232234
Token.validate(self.did_token)
233235

234236
self._assert_validate_funcs_called(setup_mocks)
@@ -239,7 +241,7 @@ def test_validate_raises_error_if_did_token_expires(self, setup_mocks):
239241
setup_mocks.epoch_time_now.return_value = \
240242
setup_mocks.claim['ext'] + 1
241243

242-
with pytest.raises(DIDTokenError) as e:
244+
with pytest.raises(DIDTokenExpired) as e:
243245
Token.validate(self.did_token)
244246

245247
self._assert_validate_funcs_called(
@@ -249,11 +251,20 @@ def test_validate_raises_error_if_did_token_expires(self, setup_mocks):
249251
assert str(e.value) == 'Given DID token has expired. Please generate a ' \
250252
'new one.'
251253

254+
def test_validate_raises_error_if_did_token_has_no_expiration(self, setup_mocks):
255+
setup_mocks.claim['ext'] = None
256+
257+
with pytest.raises(DIDTokenInvalid) as e:
258+
Token.validate(self.did_token)
259+
260+
assert str(e.value) == 'Please check the "ext" field and regenerate a new' \
261+
' token with a suitable value.'
262+
252263
def test_validate_raises_error_if_did_token_used_before_nbf(self, setup_mocks):
253264
setup_mocks.epoch_time_now.return_value = \
254265
setup_mocks.claim['nbf'] - 1
255266

256-
with pytest.raises(DIDTokenError) as e:
267+
with pytest.raises(DIDTokenInvalid) as e:
257268
Token.validate(self.did_token)
258269

259270
self._assert_validate_funcs_called(

tests/unit/utils/did_token_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from magic_admin.error import DIDTokenError
3+
from magic_admin.error import DIDTokenMalformed
44
from magic_admin.utils.did_token import construct_issuer_with_public_address
55
from magic_admin.utils.did_token import parse_public_address_from_issuer
66
from testing.data.did_token import issuer
@@ -15,7 +15,7 @@ def test_parse_public_address_from_issuer(self):
1515
assert parse_public_address_from_issuer(issuer) == public_address
1616

1717
def test_parse_public_address_from_issuer_raises_error(self):
18-
with pytest.raises(DIDTokenError) as e:
18+
with pytest.raises(DIDTokenMalformed) as e:
1919
parse_public_address_from_issuer(self.malformed_issuer)
2020

2121
assert str(e.value) == \

0 commit comments

Comments
 (0)