소스 검색

feat(api): shorten auth action codes, fixes #451

To shorten auth action codes, this both removes their double base64 encoding
(crypto.encrypt already returns an urlsafe_b64encode'd value*) and strips the
base64 padding. Any missing padding is added before processing of a code.

[*] https://github.com/pyca/cryptography/blob/0c7592c34fd58f0634f493d6ce542ab35d940b26/src/cryptography/fernet.py#L72

There is an exception handler in _unpack_code that implements a retry
mechanism so that legacy codes with double base64 encoding still work. This
does not have a separate test implementation, but was tested by running the
test suite after making the changes to _unpack_code, without having touched
_pack_code.
Peter Thomassen 4 년 전
부모
커밋
1e3c3c873a
1개의 변경된 파일13개의 추가작업 그리고 7개의 파일을 삭제
  1. 13 7
      api/desecapi/serializers.py

+ 13 - 7
api/desecapi/serializers.py

@@ -2,7 +2,7 @@ import binascii
 import copy
 import json
 import re
-from base64 import urlsafe_b64decode, urlsafe_b64encode, b64encode
+from base64 import urlsafe_b64decode, b64encode
 
 import django.core.exceptions
 from captcha.audio import AudioCaptcha
@@ -710,15 +710,21 @@ class AuthenticatedActionSerializer(serializers.ModelSerializer):
     @classmethod
     def _pack_code(cls, data):
         payload = json.dumps(data).encode()
-        payload_enc = crypto.encrypt(payload, context='desecapi.serializers.AuthenticatedActionSerializer')
-        return urlsafe_b64encode(payload_enc).decode()
+        code = crypto.encrypt(payload, context='desecapi.serializers.AuthenticatedActionSerializer').decode()
+        return code.rstrip('=')
 
     @classmethod
-    def _unpack_code(cls, code, *, ttl):
+    def _unpack_code(cls, code, *, ttl, _retry=True):
+        code += -len(code) % 4 * '='
         try:
-            payload_enc = urlsafe_b64decode(code.encode())
-            payload = crypto.decrypt(payload_enc, context='desecapi.serializers.AuthenticatedActionSerializer', ttl=ttl)
+            payload = crypto.decrypt(code.encode(), context='desecapi.serializers.AuthenticatedActionSerializer',
+                                     ttl=ttl)
             return json.loads(payload.decode())
+        except ValueError:  # TODO remove this once all urlsafe_b64encode'd codes have expired (~30d after deployment)
+            if _retry:
+                return cls._unpack_code(urlsafe_b64decode(code.encode()).decode(), ttl=ttl, _retry=False)
+            else:
+                raise
         except (TypeError, UnicodeDecodeError, UnicodeEncodeError, json.JSONDecodeError, binascii.Error):
             raise ValueError
 
@@ -749,7 +755,7 @@ class AuthenticatedActionSerializer(serializers.ModelSerializer):
                 msg = 'This code is invalid.'
             else:
                 msg = f'This code is invalid, possibly because it expired (validity: {validity_period}).'
-            raise serializers.ValidationError({api_settings.NON_FIELD_ERRORS_KEY: msg, 'code': 'invalid_code'})
+            raise serializers.ValidationError({api_settings.NON_FIELD_ERRORS_KEY: msg})
 
         # add extra fields added by the user
         unpacked_data.update(**data)