mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
increase coverage, removed old workarround
This commit is contained in:
@@ -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
|
||||
self.context["fname"] = fname
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user