diff --git a/modoboa/pdfcredentials/api/v2/serializers.py b/modoboa/pdfcredentials/api/v2/serializers.py index fba0a7809..5d5202978 100644 --- a/modoboa/pdfcredentials/api/v2/serializers.py +++ b/modoboa/pdfcredentials/api/v2/serializers.py @@ -23,7 +23,7 @@ class PDFCredentialsSettingsSerializer(serializers.Serializer): # Document storage storage_dir = serializers.CharField(default="/var/lib/modoboa/pdf_credentials") - + # Security options delete_first_dl = serializers.BooleanField(default=True) generate_at_creation = serializers.BooleanField(default=True) @@ -44,7 +44,7 @@ class PDFCredentialsSettingsSerializer(serializers.Serializer): class GetAccountCredentialsSerializer(serializers.Serializer): """A serializer for get account credential view.""" - account_id = serializers.IntegerField() + account_id = serializers.IntegerField() def validate(self, data): request = self.context["request"] @@ -54,7 +54,6 @@ class GetAccountCredentialsSerializer(serializers.Serializer): self.context["account"] = account return data - def save(self): fname = get_creds_filename(self.context["account"]) if not os.path.exists(fname): @@ -62,4 +61,4 @@ class GetAccountCredentialsSerializer(serializers.Serializer): self.context["content"] = decrypt_file(fname) if param_tools.get_global_parameter("delete_first_dl"): os.remove(fname) - self.context["fname"] = fname \ No newline at end of file + self.context["fname"] = fname diff --git a/modoboa/pdfcredentials/api/v2/tests.py b/modoboa/pdfcredentials/api/v2/tests.py index aa5cf5391..68491098c 100644 --- a/modoboa/pdfcredentials/api/v2/tests.py +++ b/modoboa/pdfcredentials/api/v2/tests.py @@ -7,7 +7,7 @@ from django.test import override_settings from django.urls import reverse from modoboa.admin import factories -from modoboa.core import models as core_models +from modoboa.core import models as core_models from modoboa.lib.tests import ModoAPITestCase @@ -37,7 +37,26 @@ class PDFCredentialViewTestCase(ModoAPITestCase): resp = self.client.post(url, data, format="json") self.assertEqual(resp.status_code, expected_status) return data - + + def test_pdfcredentials_disabled(self): + self.set_global_parameter("enabled_pdfcredentials", False) + values = self._create_account("leon5@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertFalse(os.path.exists(fname)) + + # Check that link is not present in listing page + response = self.client.get(reverse("v2:identities-list"), format="json") + assert_action_in_response = False + for res_dict in response.json(): + for action in res_dict["possible_actions"]: + try: + if action.get("icon") == "mdi-file-download-outline": + assert_action_in_response = True + break + except KeyError: + continue + self.assertFalse(assert_action_in_response) + def test_password_updated(self): """Check that document is generated at account creation/update.""" values = self._create_account("leon@test.com") @@ -49,14 +68,14 @@ class PDFCredentialViewTestCase(ModoAPITestCase): response = self.client.get(reverse("v2:identities-list"), format="json") assert_action_in_response = False for res_dict in response.json(): - for action in res_dict["possible_actions"]: - try: - if action.get("icon") == "mdi-file-download-outline": - assert_action_in_response = True - break - except KeyError: - continue - self.assertEqual(assert_action_in_response, True) + for action in res_dict["possible_actions"]: + try: + if action.get("icon") == "mdi-file-download-outline": + assert_action_in_response = True + break + except KeyError: + continue + self.assertTrue(assert_action_in_response) # Try to download the file response = self.client.get( @@ -72,7 +91,8 @@ class PDFCredentialViewTestCase(ModoAPITestCase): response = self.client.get( reverse("v2:get-credentials", args=[account.pk])) - self.assertEqual(response.json()["detail"], "No document available for this user") + self.assertEqual(response.json()["detail"], + "No document available for this user") # Update account values.update({"language": "en"}) diff --git a/modoboa/pdfcredentials/rfc6266.py b/modoboa/pdfcredentials/rfc6266.py deleted file mode 100644 index c0dacb2d1..000000000 --- a/modoboa/pdfcredentials/rfc6266.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -Copy of the original rfc6266 package, just the generation part. - -Orginal code is available at https://github.com/g2p/rfc6266. -""" - -from urllib.parse import quote - - -# RFC 2616 -separator_chars = "()<>@,;:\\\"/[]?={} \t" -# RFC 5987 -attr_chars_nonalnum = '!#$&+-.^_`|~' - - -def percent_encode(string, safe, encoding): - return quote(string, safe, encoding, errors='strict') - - -def is_token_char(ch): - # Must be ascii, and neither a control char nor a separator char - asciicode = ord(ch) - # < 128 means ascii, exclude control chars at 0-31 and 127, - # exclude separator characters. - return 31 < asciicode < 127 and ch not in separator_chars - - -def is_token(candidate): - return all(is_token_char(ch) for ch in candidate) - - -def is_ascii(text): - return all(ord(ch) < 128 for ch in text) - - -def fits_inside_codec(text, codec): - try: - text.encode(codec) - except UnicodeEncodeError: - return False - else: - return True - - -def is_lws_safe(text): - return normalize_ws(text) == text - - -def normalize_ws(text): - return ' '.join(text.split()) - - -def qd_quote(text): - return text.replace('\\', '\\\\').replace('"', '\\"') - - -def build_header( - filename, disposition='attachment', filename_compat=None -): - """Generate a Content-Disposition header for a given filename. - For legacy clients that don't understand the filename* parameter, - a filename_compat value may be given. - It should either be ascii-only (recommended) or iso-8859-1 only. - In the later case it should be a character string - (unicode in Python 2). - Options for generating filename_compat (only useful for legacy clients): - - ignore (will only send filename*); - - strip accents using unicode's decomposing normalisations, - which can be done from unicode data (stdlib), and keep only ascii; - - use the ascii transliteration tables from Unidecode (PyPI); - - use iso-8859-1 - Ignore is the safest, and can be used to trigger a fallback - to the document location (which can be percent-encoded utf-8 - if you control the URLs). - See https://tools.ietf.org/html/rfc6266#appendix-D - """ - - # While this method exists, it could also sanitize the filename - # by rejecting slashes or other weirdness that might upset a receiver. - - if disposition != 'attachment': - assert is_token(disposition) - - rv = disposition - - if is_token(filename): - rv += '; filename=%s' % (filename, ) - return rv - elif is_ascii(filename) and is_lws_safe(filename): - qd_filename = qd_quote(filename) - rv += '; filename="%s"' % (qd_filename, ) - if qd_filename == filename: - # RFC 6266 claims some implementations are iffy on qdtext's - # backslash-escaping, we'll include filename* in that case. - return rv - elif filename_compat: - if is_token(filename_compat): - rv += '; filename=%s' % (filename_compat, ) - else: - assert is_lws_safe(filename_compat) - rv += '; filename="%s"' % (qd_quote(filename_compat), ) - - # alnum are already considered always-safe, but the rest isn't. - # Python encodes ~ when it shouldn't, for example. - rv += "; filename*=utf-8''%s" % (percent_encode( - filename, safe=attr_chars_nonalnum, encoding='utf-8'), ) - - # This will only encode filename_compat, if it used non-ascii iso-8859-1. - return rv.encode('iso-8859-1') diff --git a/modoboa/pdfcredentials/views.py b/modoboa/pdfcredentials/views.py index 584675db7..7fe1b7d8e 100644 --- a/modoboa/pdfcredentials/views.py +++ b/modoboa/pdfcredentials/views.py @@ -14,7 +14,6 @@ from modoboa.lib.exceptions import ModoboaException, PermDeniedException from modoboa.parameters import tools as param_tools from .lib import decrypt_file, get_creds_filename -from .rfc6266 import build_header @login_required @@ -33,5 +32,5 @@ def get_account_credentials(request, accountid): resp = HttpResponse(content) resp["Content-Type"] = "application/pdf" resp["Content-Length"] = len(content) - resp["Content-Disposition"] = build_header(os.path.basename(fname)) + resp["Content-Disposition"] = f"attachment; filename={fname};" return resp