From 00a6b638418e49c476217eec096c8fb271ed204e Mon Sep 17 00:00:00 2001 From: Spitap Date: Fri, 17 Feb 2023 18:09:11 +0100 Subject: [PATCH] added pdfcredential native support --- .../components/identities/IdentityList.vue | 11 + modoboa/admin/api/v2/serializers.py | 30 ++- modoboa/admin/signals.py | 1 + modoboa/core/app_settings.py | 4 +- .../core/commands/templates/settings.py.tpl | 1 + modoboa/pdfcredentials/__init__.py | 3 + modoboa/pdfcredentials/api/v2/__init__.py | 0 modoboa/pdfcredentials/api/v2/handlers.py | 32 +++ modoboa/pdfcredentials/api/v2/serializers.py | 65 +++++ modoboa/pdfcredentials/api/v2/tests.py | 153 +++++++++++ modoboa/pdfcredentials/api/v2/urls.py | 11 + modoboa/pdfcredentials/api/v2/views.py | 39 +++ modoboa/pdfcredentials/app_settings.py | 243 ++++++++++++++++++ modoboa/pdfcredentials/apps.py | 35 +++ modoboa/pdfcredentials/constants.py | 10 + modoboa/pdfcredentials/documents.py | 131 ++++++++++ modoboa/pdfcredentials/forms.py | 146 +++++++++++ modoboa/pdfcredentials/handlers.py | 53 ++++ modoboa/pdfcredentials/lib.py | 101 ++++++++ .../locale/cs_CZ/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/de/LC_MESSAGES/django.po | 212 +++++++++++++++ .../locale/el_GR/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/en/LC_MESSAGES/django.po | 209 +++++++++++++++ .../locale/fr/LC_MESSAGES/django.po | 210 +++++++++++++++ .../locale/it/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/ja_JP/LC_MESSAGES/django.po | 210 +++++++++++++++ .../locale/nl_NL/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/pl_PL/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/pt_BR/LC_MESSAGES/django.po | 211 +++++++++++++++ .../locale/pt_PT/LC_MESSAGES/django.po | 209 +++++++++++++++ .../locale/sv/LC_MESSAGES/django.po | 211 +++++++++++++++ modoboa/pdfcredentials/models.py | 3 + modoboa/pdfcredentials/rfc6266.py | 109 ++++++++ modoboa/pdfcredentials/tests.py | 147 +++++++++++ modoboa/pdfcredentials/urls.py | 10 + modoboa/pdfcredentials/views.py | 37 +++ modoboa/urls.py | 1 + modoboa/urls_api_v2.py | 1 + requirements.txt | 3 + test_project/test_project/settings.py | 1 + 40 files changed, 3904 insertions(+), 4 deletions(-) create mode 100644 modoboa/pdfcredentials/__init__.py create mode 100644 modoboa/pdfcredentials/api/v2/__init__.py create mode 100644 modoboa/pdfcredentials/api/v2/handlers.py create mode 100644 modoboa/pdfcredentials/api/v2/serializers.py create mode 100644 modoboa/pdfcredentials/api/v2/tests.py create mode 100644 modoboa/pdfcredentials/api/v2/urls.py create mode 100644 modoboa/pdfcredentials/api/v2/views.py create mode 100644 modoboa/pdfcredentials/app_settings.py create mode 100644 modoboa/pdfcredentials/apps.py create mode 100644 modoboa/pdfcredentials/constants.py create mode 100644 modoboa/pdfcredentials/documents.py create mode 100644 modoboa/pdfcredentials/forms.py create mode 100644 modoboa/pdfcredentials/handlers.py create mode 100644 modoboa/pdfcredentials/lib.py create mode 100644 modoboa/pdfcredentials/locale/cs_CZ/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/de/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/el_GR/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/en/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/fr/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/it/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/ja_JP/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/nl_NL/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/pt_PT/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/locale/sv/LC_MESSAGES/django.po create mode 100644 modoboa/pdfcredentials/models.py create mode 100644 modoboa/pdfcredentials/rfc6266.py create mode 100644 modoboa/pdfcredentials/tests.py create mode 100644 modoboa/pdfcredentials/urls.py create mode 100644 modoboa/pdfcredentials/views.py diff --git a/frontend/src/components/identities/IdentityList.vue b/frontend/src/components/identities/IdentityList.vue index 1a10fb245..991fba2fc 100644 --- a/frontend/src/components/identities/IdentityList.vue +++ b/frontend/src/components/identities/IdentityList.vue @@ -164,6 +164,17 @@ export default { getMenuItems (item) { const result = [] if (item.type === 'account') { + item.possible_actions.forEach(element => { + result.push({ + label: this.$gettext(element.label), + icon: element.icon, + link: element.url, + onClick: () => { + window.open(element.url) + this.fetchIdentities() + } + }) + }) result.push({ label: this.$gettext('Edit'), icon: 'mdi-circle-edit-outline', onClick: this.editAccount }) result.push({ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteAccount, color: 'red' }) } else if (item.type === 'alias') { diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py index 84bccec49..a6d2c0e06 100644 --- a/modoboa/admin/api/v2/serializers.py +++ b/modoboa/admin/api/v2/serializers.py @@ -14,6 +14,7 @@ from django.contrib.auth import password_validation from rest_framework import serializers +from modoboa.admin import signals as admin_signals from modoboa.admin.api.v1 import serializers as v1_serializers from modoboa.core import models as core_models, signals as core_signals from modoboa.lib import email_utils @@ -284,6 +285,15 @@ class TagSerializer(serializers.Serializer): type = serializers.CharField() +class IdPossibleActionsSerializer(serializers.Serializer): + """Serialiser used to get possible actions related to identities.""" + + name = serializers.CharField() + icon = serializers.CharField() + label = serializers.CharField() + url = serializers.URLField() + + class IdentitySerializer(serializers.Serializer): """Serializer used for identities.""" @@ -293,6 +303,24 @@ class IdentitySerializer(serializers.Serializer): name_or_rcpt = serializers.CharField() tags = TagSerializer(many=True) + def __init__(self, instance=None, data=..., **kwargs): + super().__init__(instance, data, **kwargs) + self.fields["possible_actions"] = serializers.SerializerMethodField() + + def get_possible_actions(self, identity): + if not isinstance(identity, core_models.User): + # Return empty action list if identity type is an alias (not used for now) + return [] + actions = admin_signals.extra_account_identities_actions.send( + self.__class__, account=identity) + cleaned_actions = [] + for action in actions: + try : + serialized_data = IdPossibleActionsSerializer(action[1]).data + cleaned_actions.append(serialized_data) + except (ValidationError, AttributeError): + continue + return cleaned_actions class AccountResourceSerializer(serializers.ModelSerializer): """Serializer for user resource.""" @@ -512,7 +540,7 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer): instance.set_password(password) if mailbox_data: creator = self.context["request"].user - if hasattr(instance, "mailbox"): + if hasattr(instance, "mailbox") and "mailbox" in validated_data: if "username" in validated_data: mailbox_data["email"] = validated_data["username"] instance.email = validated_data["username"] diff --git a/modoboa/admin/signals.py b/modoboa/admin/signals.py index d7e59ba9b..9610ec7bd 100644 --- a/modoboa/admin/signals.py +++ b/modoboa/admin/signals.py @@ -29,3 +29,4 @@ get_domain_form_instances = django.dispatch.Signal( import_object = django.dispatch.Signal(providing_args=["objtype"]) use_external_recipients = django.dispatch.Signal(providing_args=["recipients"]) new_dkim_keys = django.dispatch.Signal(providing_args=["domains"]) +extra_account_identities_actions = django.dispatch.Signal(providing_args=["account"]) \ No newline at end of file diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 7b4aaeee1..5a1fcdc5a 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -1,7 +1,5 @@ """Core settings.""" -from collections import OrderedDict - import collections from django import forms @@ -536,7 +534,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): ] def _add_dynamic_fields(self): - new_fields = OrderedDict() + new_fields = collections.OrderedDict() for field, value in self.fields.items(): new_fields[field] = value if field == "sms_provider": diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index bb9baf7d5..358ac4fe8 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -87,6 +87,7 @@ MODOBOA_APPS = ( 'modoboa.dnstools', 'modoboa.policyd', 'modoboa.maillog', + 'modoboa.pdfcredentials', # Modoboa extensions here. {% for extension in extensions %} '{{ extension }}', {% endfor %} diff --git a/modoboa/pdfcredentials/__init__.py b/modoboa/pdfcredentials/__init__.py new file mode 100644 index 000000000..7e1dfa1a8 --- /dev/null +++ b/modoboa/pdfcredentials/__init__.py @@ -0,0 +1,3 @@ +"""Generate PDF documents containing user credentials.""" + +default_app_config = "modoboa.pdfcredentials.apps.PDFCredentialsConfig" diff --git a/modoboa/pdfcredentials/api/v2/__init__.py b/modoboa/pdfcredentials/api/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/pdfcredentials/api/v2/handlers.py b/modoboa/pdfcredentials/api/v2/handlers.py new file mode 100644 index 000000000..453136216 --- /dev/null +++ b/modoboa/pdfcredentials/api/v2/handlers.py @@ -0,0 +1,32 @@ +"""Handlers for PDF credientials api v2.""" + +import os + +from django.urls import reverse +from django.dispatch import receiver +from django.utils.translation import ugettext as _ + +from modoboa.admin import signals as admin_signals +from modoboa.parameters import tools as param_tools + +from ...lib import get_creds_filename + + +@receiver(admin_signals.extra_account_identities_actions) +def extra_identities_actions(sender, account, **kwargs): + """ + Add download credential action for identity. + Used for api v2. + """ + if not param_tools.get_global_parameter("enabled_pdfcredentials"): + return [] + fname = get_creds_filename(account) + if not os.path.exists(fname): + return [] + return { + "name": "get_credentials", + "url": reverse("v2:get-credentials", + args=[account.id]), + "icon": "mdi-file-download-outline", + "label": _("Download PDF credentials.") + } diff --git a/modoboa/pdfcredentials/api/v2/serializers.py b/modoboa/pdfcredentials/api/v2/serializers.py new file mode 100644 index 000000000..fba0a7809 --- /dev/null +++ b/modoboa/pdfcredentials/api/v2/serializers.py @@ -0,0 +1,65 @@ +"""Limits serializers for API v2.""" + +import os + +from django.utils.translation import ugettext_lazy as _ + +from rest_framework import serializers, status +from rest_framework.exceptions import PermissionDenied, APIException + + +from modoboa.core.models import User +from modoboa.parameters import tools as param_tools +from ...lib import decrypt_file, get_creds_filename + +from ...constants import CONNECTION_SECURITY_MODES + + +class PDFCredentialsSettingsSerializer(serializers.Serializer): + """A serializer for global parameters.""" + + # General + enabled_pdfcredentials = serializers.BooleanField(default=True) + + # 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) + + # Customization options + title = serializers.CharField(default=_("Personal account information")) + webpanel_url = serializers.URLField() + custom_message = serializers.CharField(required=False, allow_blank=True) + include_connection_settings = serializers.BooleanField(default=False) + smtp_server_address = serializers.CharField() + smtp_server_port = serializers.IntegerField(default=587) + smtp_connection_security = serializers.ChoiceField(choices=CONNECTION_SECURITY_MODES, default="starttls") + imap_server_address = serializers.CharField() + imap_server_port = serializers.IntegerField(default=143) + imap_connection_security = serializers.ChoiceField(choices=CONNECTION_SECURITY_MODES, default="starttls") + + +class GetAccountCredentialsSerializer(serializers.Serializer): + """A serializer for get account credential view.""" + + account_id = serializers.IntegerField() + + def validate(self, data): + request = self.context["request"] + account = User.objects.get(pk=data["account_id"]) + if not request.user.can_access(account): + raise PermissionDenied() + self.context["account"] = account + return data + + + def save(self): + fname = get_creds_filename(self.context["account"]) + if not os.path.exists(fname): + raise APIException( _("No document available for this user"), status.HTTP_400_BAD_REQUEST) + 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 diff --git a/modoboa/pdfcredentials/api/v2/tests.py b/modoboa/pdfcredentials/api/v2/tests.py new file mode 100644 index 000000000..c970eaf9a --- /dev/null +++ b/modoboa/pdfcredentials/api/v2/tests.py @@ -0,0 +1,153 @@ +"""API v2 tests.""" + +import tempfile +import shutil +import os + +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.lib.tests import ModoAPITestCase + + +class PDFCredentialViewTestCase(ModoAPITestCase): + + @classmethod + def setUpTestData(cls): # NOQA:N802 + """Create test data.""" + super().setUpTestData() + factories.populate_database() + + def setUp(self): + """Create temp. directory to store files.""" + super().setUp() + self.workdir = tempfile.mkdtemp() + self.set_global_parameter("storage_dir", self.workdir) + + def tearDown(self): + """Reset test env.""" + shutil.rmtree(self.workdir) + + def _create_account(self, username, expected_status=201): + url = reverse("v2:account-list") + data = { + "username": f"{username}", + "role": "SimpleUsers", + "mailbox": { + "use_domain_quota": True + }, + "password": "Toto12345", + "language": "fr", + } + resp = self.client.post(url, data, format="json") + self.assertEqual(resp.status_code, expected_status) + return data + + def test_password_updated(self): + """Check that document is generated at account creation/update.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + + # Check if link is 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.assertEqual(assert_action_in_response, True) + + # Try to download the file + response = self.client.get( + reverse("v2:get-credentials", + args=[account.pk])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "application/pdf") + + # File have been deleted? + self.assertFalse(os.path.exists(fname)) + + # Try to download a second time + response = self.client.get( + reverse("v2:get-credentials", + args=[account.pk])) + self.assertEqual(response.json()["detail"], "No document available for this user") + + # Update account + values.update({"language": "en"}) + self.client.patch( + reverse("v2:account-detail", args=[account.pk]), values, format='json' + ) + self.assertFalse(os.path.exists(fname)) + + self.set_global_parameter("generate_at_creation", False) + self.client.patch( + reverse("v2:account-detail", args=[account.pk]), values, format='json' + ) + self.assertTrue(os.path.exists(fname)) + + def test_with_connection_settings(self): + """Add connection settings to documents.""" + self.set_global_parameter("include_connection_settings", True) + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_with_custom_message(self): + """Add custom message to documents.""" + self.set_global_parameter("custom_message", "This is a test message.") + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_account_delete(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + url = reverse("v2:account-delete", args=[account.pk]) + self.client.post(url) + self.assertFalse(os.path.exists(fname)) + + @override_settings(MODOBOA_LOGO="modoboa.png") + def test_with_custom_logo(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_download_and_delete_account(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + + # Try to download the file + response = self.client.get( + reverse("v2:get-credentials", + args=[account.pk])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "application/pdf") + + # File have been deleted? + self.assertFalse(os.path.exists(fname)) + + # Delete account + url = reverse("v2:account-delete", args=[account.pk]) + self.client.post(url) + self.assertFalse(os.path.exists(fname)) + + def test_storage_dir_creation(self): + """Test storage directory creation.""" + self.set_global_parameter("storage_dir", "/nonexistentdir") + self._create_account("leon@test.com", expected_status=500) diff --git a/modoboa/pdfcredentials/api/v2/urls.py b/modoboa/pdfcredentials/api/v2/urls.py new file mode 100644 index 000000000..4b62a3ea0 --- /dev/null +++ b/modoboa/pdfcredentials/api/v2/urls.py @@ -0,0 +1,11 @@ +"""PDF Credentials API urls.""" + +from django.urls import path + + +from . import views + +urlpatterns = [ + path('credentials//', views.PDFCredentialView.as_view(), + name="get-credentials"), +] diff --git a/modoboa/pdfcredentials/api/v2/views.py b/modoboa/pdfcredentials/api/v2/views.py new file mode 100644 index 000000000..eb8b844eb --- /dev/null +++ b/modoboa/pdfcredentials/api/v2/views.py @@ -0,0 +1,39 @@ +"""PDF Credentials API v2 views.""" + +from django.utils.translation import ugettext as _ +from django.http import HttpResponse + +from rest_framework import permissions +from rest_framework.views import APIView + +from modoboa.admin.models import Domain + +from .serializers import GetAccountCredentialsSerializer + + +class PDFCredentialView(APIView): + + permission_classes = ( + permissions.IsAuthenticated, permissions.DjangoModelPermissions, + ) + + serializer_class = GetAccountCredentialsSerializer + + def get_queryset(self): + """Filter queryset based on current user.""" + return Domain.objects.get_for_admin(self.request.user) + + def get(self, request, *args, **kwargs): + """View to download a document.""" + data = {"account_id": kwargs["account_id"]} + serializer = GetAccountCredentialsSerializer(data=data, context={'request': request}) + serializer.is_valid(raise_exception=True) + serializer.save() + + content = serializer.context["content"] + fname = serializer.context["fname"] + resp = HttpResponse(content) + resp["Content-Type"] = "application/pdf" + resp["Content-Length"] = len(content) + resp["Content-Disposition"] = f"attachment; filename={fname};" + return resp diff --git a/modoboa/pdfcredentials/app_settings.py b/modoboa/pdfcredentials/app_settings.py new file mode 100644 index 000000000..b5bd958ba --- /dev/null +++ b/modoboa/pdfcredentials/app_settings.py @@ -0,0 +1,243 @@ +"""PDF Credentials forms.""" + +import collections + +from django import forms +from django.conf import settings +from django.utils.functional import cached_property +from django.utils.translation import ugettext_lazy as _ + +from django.contrib.sites import models as sites_models + +from modoboa.lib import form_utils +from modoboa.parameters import forms as param_forms + +from . import constants + + +PDF_CREDENTIALS_PARAMETERS_STRUCT = collections.OrderedDict([ + ("general", { + "label": _("General"), + "params": collections.OrderedDict([ + ("enabled_pdfcredentials", { + "label": _("Enable PDF credentials"), + "help_text": _( + "Enabled PDF credentials document generation on account creation."), + }) + ]) + }), + ("docstore", { + "label": _("Documents storage"), + "params": collections.OrderedDict([ + ("storage_dir", { + "label": _("Directory to save documents into"), + "help_text": _( + "Path to a directory where PDF documents will be saved"), + }), + ]) + }), + ("security", { + "label": _("Security options"), + "params": collections.OrderedDict([ + ("delete_first_dl", { + "label": _("Delete documents after the first download"), + "help_text": _( + "Automatically delete a document just after its first download " + "from this interface" + ) + }), + ("generate_at_creation", { + "label": _("Generate documents only at account creation"), + "help_text": _( + "Generate a new document only when a new account is created. " + "If set to no, a new document will be created each time a " + "password is updated." + ) + }) + ]) + }), + ("customization", { + "label": _("Customization options"), + "params": collections.OrderedDict([ + ("title", { + "label": _("Title"), + "help_text": _("The document's title") + }), + ("webpanel_url", { + "label": _("Web panel url"), + "help_text": _("URL of the Modoboa web panel") + }), + ("custom_message", { + "label": _("Custom message"), + "help_text": _( + "A custom message that will appear at the end of documents" + ) + }), + ("include_connection_settings", { + "label": _("Include mail client connection settings"), + "help_text": _( + "Include required SMTP and IMAP connection information to " + "configure a mail client, a tablet or a phone" + ) + }), + ("smtp_server_address", { + "label": _("SMTP server address"), + "help_text": _("Address of the SMTP server (hostname or IP)") + }), + ("smtp_server_port", { + "label": _("SMTP server port"), + "help_port": _("Listening port of the SMTP server") + }), + ("smtp_connection_security", { + "label": _("SMTP connection security"), + "help_text": _("Connection security mechanism") + }), + ("imap_server_address", { + "label": _("IMAP server address"), + "help_text": _("Address of the IMAP server (hostname or IP)") + }), + ("imap_server_port", { + "label": _("IMAP server port"), + "help_text": _("Listening port of the IMAP server") + }), + ("imap_connection_security", { + "label": _("IMAP connection security"), + "help_text": _("Connection security mechanism") + }) + ]) + }) +]) + + + +class ParametersForm(param_forms.AdminParametersForm): + """Admin parameters form.""" + + app = "pdfcredentials" + + general = form_utils.SeparatorField(label=_("General")) + + enabled_pdfcredentials = form_utils.YesNoField( + label= _("Enable PDF credentials"), + initial=True, + help_text=_("Enabled PDF credentials document generation on account creation.") + ) + + docstore = form_utils.SeparatorField(label=_("Documents storage")) + + storage_dir = forms.CharField( + label=_("Directory to save documents into"), + initial="/var/lib/modoboa/pdf_credentials", + help_text=_("Path to a directory where PDF documents will be saved"), + ) + + security = form_utils.SeparatorField(label=_("Security options")) + + delete_first_dl = form_utils.YesNoField( + label=_("Delete documents after the first download"), + initial=True, + help_text=_( + "Automatically delete a document just after its first download " + "from this interface" + ) + ) + + generate_at_creation = form_utils.YesNoField( + label=_("Generate documents only at account creation"), + initial=True, + help_text=_( + "Generate a new document only when a new account is created. " + "If set to no, a new document will be created each time a " + "password is updated." + ) + ) + + customization = form_utils.SeparatorField(label=_("Customization options")) + + title = forms.CharField( + label=_("Title"), + initial=_("Personal account information"), + help_text=_("The document's title") + ) + + webpanel_url = forms.URLField( + label=_("Web panel url"), + help_text=_("URL of the Modoboa web panel") + ) + + custom_message = forms.CharField( + label=_("Custom message"), + help_text=_( + "A custom message that will appear at the end of documents"), + required=False + ) + + include_connection_settings = form_utils.YesNoField( + label=_("Include mail client connection settings"), + initial=False, + help_text=_( + "Include required SMTP and IMAP connection information to " + "configure a mail client, a tablet or a phone" + ) + ) + + smtp_server_address = forms.CharField( + label=_("SMTP server address"), + help_text=_("Address of the SMTP server (hostname or IP)") + ) + + smtp_server_port = forms.IntegerField( + label=_("SMTP server port"), + initial=587, + help_text=_("Listening port of the SMTP server") + ) + + smtp_connection_security = forms.ChoiceField( + label=_("SMTP connection security"), + choices=constants.CONNECTION_SECURITY_MODES, + initial="starttls", + help_text=_("Connection security mechanism") + ) + + imap_server_address = forms.CharField( + label=_("IMAP server address"), + help_text=_("Address of the IMAP server (hostname or IP)") + ) + + imap_server_port = forms.IntegerField( + label=_("IMAP server port"), + initial=143, + help_text=_("Listening port of the IMAP server") + ) + + imap_connection_security = forms.ChoiceField( + label=_("IMAP connection security"), + choices=constants.CONNECTION_SECURITY_MODES, + initial="starttls", + help_text=_("Connection security mechanism") + ) + + visibility_rules = { + "smtp_server_address": "include_connection_settings=True", + "smtp_server_port": "include_connection_settings=True", + "smtp_connection_security": "include_connection_settings=True", + "imap_server_address": "include_connection_settings=True", + "imap_server_port": "include_connection_settings=True", + "imap_connection_security": "include_connection_settings=True", + } + + @cached_property + def hostname(self): + """Return local hostname.""" + return sites_models.Site.objects.get_current().domain + + def __init__(self, *args, **kwargs): + """Set initial values.""" + super(ParametersForm, self).__init__(*args, **kwargs) + if not self.fields["webpanel_url"].initial: + url = "https://{}{}".format(self.hostname, settings.LOGIN_URL) + self.fields["webpanel_url"].initial = url + if not self.fields["smtp_server_address"].initial: + self.fields["smtp_server_address"].initial = self.hostname + if not self.fields["imap_server_address"].initial: + self.fields["imap_server_address"].initial = self.hostname diff --git a/modoboa/pdfcredentials/apps.py b/modoboa/pdfcredentials/apps.py new file mode 100644 index 000000000..761eb4fe1 --- /dev/null +++ b/modoboa/pdfcredentials/apps.py @@ -0,0 +1,35 @@ +"""AppConfig for PDF credentials.""" + +from django.apps import AppConfig + +from django.utils.translation import ugettext_lazy + + +def load_pdfcredential_settings(): + """Load core settings. + + This function must be manually called (see :file:`urls.py`) in + order to load base settings. + """ + from modoboa.parameters import tools as param_tools + from . import app_settings + from .api.v2 import serializers + + param_tools.registry.add( + "global", app_settings.ParametersForm, ugettext_lazy("PDF credentials")) + param_tools.registry.add2( + "global", "pdfcredentials", ugettext_lazy("PDF Credentials"), + app_settings.PDF_CREDENTIALS_PARAMETERS_STRUCT, + serializers.PDFCredentialsSettingsSerializer) + + +class PDFCredentialsConfig(AppConfig): + """App configuration.""" + + name = "modoboa.pdfcredentials" + verbose_name = "PDF credentials for Modoboa" + + def ready(self): + from . import handlers + from .api.v2 import handlers + load_pdfcredential_settings() diff --git a/modoboa/pdfcredentials/constants.py b/modoboa/pdfcredentials/constants.py new file mode 100644 index 000000000..c0a407a9c --- /dev/null +++ b/modoboa/pdfcredentials/constants.py @@ -0,0 +1,10 @@ +"""PDF credentials constants.""" + +from django.utils.translation import ugettext_lazy + + +CONNECTION_SECURITY_MODES = [ + ("none", ugettext_lazy("None")), + ("starttls", "STARTTLS"), + ("tls", "TLS") +] diff --git a/modoboa/pdfcredentials/documents.py b/modoboa/pdfcredentials/documents.py new file mode 100644 index 000000000..21399a6d6 --- /dev/null +++ b/modoboa/pdfcredentials/documents.py @@ -0,0 +1,131 @@ +"""The code used to generate PDF files (based on Reportlab).""" + +from io import BytesIO + +from reportlab.lib import colors +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import cm +from reportlab.lib.enums import TA_CENTER +from reportlab.lib import utils +from reportlab.platypus import ( + SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Frame, + Image +) +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + +from django.utils.translation import ugettext as _ + +from modoboa.parameters import tools as param_tools + +from . import lib + +styles = getSampleStyleSheet() +styles.add(ParagraphStyle( + name="Footer", alignment=TA_CENTER, fontName="Helvetica-Oblique", + textColor=colors.lightgrey +)) +styles.add(ParagraphStyle( + name="Warning", fontName="Helvetica-Oblique", + textColor=colors.red, fontSize=12 +)) +styles.add(ParagraphStyle( + name="Link", + textColor=colors.blue +)) +styles.add(ParagraphStyle( + name="Greeting", alignment=TA_CENTER, fontName="Helvetica-Oblique", + fontSize=14 +)) + + +def resized_image(path, width=1*cm): + img = utils.ImageReader(path) + iw, ih = img.getSize() + aspect = ih / float(iw) + return Image(path, width=width, height=(width * aspect)) + + +def credentials(account, password): + """Generate a PDF document containing account credentials.""" + + def page_template(canvas, doc): + canvas.setTitle(_("Personal account information")) + canvas.setAuthor(account.fullname) + canvas.setCreator("Modoboa") + footer = [Paragraph(_("Powered by Modoboa - Mail hosting made simple"), + styles["Footer"])] + Frame(0, 0, 21 * cm, 2 * cm).addFromList(footer, canvas) + + conf = dict( + param_tools.get_global_parameters("pdfcredentials")) + filename = lib.get_creds_filename(account) + buff = BytesIO() + doc = SimpleDocTemplate(buff, pagesize=A4) + story = [] + story.append(resized_image(lib.get_document_logo(), 8*cm)) + story.append(Spacer(1, 1 * cm)) + story.append(Paragraph(conf["title"], styles["Title"])) + story.append(Spacer(1, 1 * cm)) + story.append(Paragraph(_(""" +Dear %s, this document contains the credentials you will need +to connect to Modoboa. Learn the content and destroy +the document as soon as possible. +""") % account.fullname, styles["Normal"])) + story.append(Spacer(1, 0.2 * cm)) + story.append(Paragraph(_("Web panel:"), styles["h3"])) + data = [ + ["URL", conf["webpanel_url"]], + [_("Username"), account.username], + [_("Password"), password] + ] + table = Table(data) + table.setStyle(TableStyle([ + ('TEXTCOLOR', (1, 0), (1, 0), colors.blue), + ('GRID', (0, 0), (-1, -1), 1, colors.black), + ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), + ])) + story.append(table) + story.append(Spacer(1, 0.5 * cm)) + story.append(Paragraph(_(""" +Here you can view your emails anytime online, create filters or manage +your contacts. +"""), styles["Normal"])) + + story.append(Spacer(1, 0.2 * cm)) + story.append(Paragraph( + _("Please change your password!"), + styles["Warning"])) + + if conf["include_connection_settings"]: + story.append(Spacer(1, 1 * cm)) + story.append(Paragraph( + _("PC/Tablet/Smartphone configuration:"), styles["h3"])) + story.append(Spacer(1, 0.2 * cm)) + data = [ + [_("SMTP server address"), conf["smtp_server_address"]], + [_("SMTP server port"), conf["smtp_server_port"]], + [_("SMTP connection security"), conf["smtp_connection_security"]], + [_("IMAP server address"), conf["imap_server_address"]], + [_("IMAP server port"), conf["imap_server_port"]], + [_("IMAP connection security"), conf["imap_connection_security"]], + ] + table = Table(data) + table.setStyle(TableStyle([ + ('GRID', (0, 0), (-1, -1), 1, colors.black), + ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), + ])) + story.append(table) + story.append(Spacer(1, 0.5 * cm)) + story.append(Paragraph( + _("Use those settings for your computer, tablet or phone."), + styles["Normal"]) + ) + + if conf["custom_message"]: + story.append(Spacer(1, 2 * cm)) + story.append(Paragraph(conf["custom_message"], styles["Greeting"])) + + doc.build(story, onFirstPage=page_template, onLaterPages=page_template) + length = len(buff.getvalue()) + buff.seek(0) + lib.crypt_and_save_to_file(buff, filename, length) diff --git a/modoboa/pdfcredentials/forms.py b/modoboa/pdfcredentials/forms.py new file mode 100644 index 000000000..8546a25f5 --- /dev/null +++ b/modoboa/pdfcredentials/forms.py @@ -0,0 +1,146 @@ +"""PDF credentials forms.""" + +from django import forms +from django.conf import settings +from django.utils.functional import cached_property +from django.utils.translation import ugettext_lazy as _ + +from django.contrib.sites import models as sites_models + +from modoboa.lib import form_utils +from modoboa.parameters import forms as param_forms + +from . import constants + + +class ParametersForm(param_forms.AdminParametersForm): + """Admin parameters form.""" + + app = "pdfcredentials" + + docstore = form_utils.SeparatorField(label=_("Documents storage")) + + storage_dir = forms.CharField( + label=_("Directory to save documents into"), + initial="/var/lib/modoboa/pdf_credentials", + help_text=_("Path to a directory where PDF documents will be saved"), + ) + + security = form_utils.SeparatorField(label=_("Security options")) + + delete_first_dl = form_utils.YesNoField( + label=_("Delete documents after the first download"), + initial=True, + help_text=_( + "Automatically delete a document just after its first download " + "from this interface" + ) + ) + + generate_at_creation = form_utils.YesNoField( + label=_("Generate documents only at account creation"), + initial=True, + help_text=_( + "Generate a new document only when a new account is created. " + "If set to no, a new document will be created each time a " + "password is updated." + ) + ) + + customization = form_utils.SeparatorField(label=_("Customization options")) + + title = forms.CharField( + label=_("Title"), + initial=_("Personal account information"), + help_text=_("The document's title") + ) + + webpanel_url = forms.URLField( + label=_("Web panel url"), + help_text=_("URL of the Modoboa web panel") + ) + + custom_message = forms.CharField( + label=_("Custom message"), + help_text=_( + "A custom message that will appear at the end of documents"), + required=False + ) + + include_connection_settings = form_utils.YesNoField( + label=_("Include mail client connection settings"), + initial=False, + help_text=_( + "Include required SMTP and IMAP connection information to " + "configure a mail client, a tablet or a phone" + ) + ) + + smtp_server_address = forms.CharField( + label=_("SMTP server address"), + help_text=_("Address of the SMTP server (hostname or IP)") + ) + + smtp_server_port = forms.IntegerField( + label=_("SMTP server port"), + initial=587, + help_text=_("Listening port of the SMTP server") + ) + + smtp_connection_security = forms.ChoiceField( + label=_("SMTP connection security"), + choices=constants.CONNECTION_SECURITY_MODES, + initial="starttls", + help_text=_("Connection security mechanism") + ) + + imap_server_address = forms.CharField( + label=_("IMAP server address"), + help_text=_("Address of the IMAP server (hostname or IP)") + ) + + imap_server_port = forms.IntegerField( + label=_("IMAP server port"), + initial=143, + help_text=_("Listening port of the IMAP server") + ) + + imap_connection_security = forms.ChoiceField( + label=_("IMAP connection security"), + choices=constants.CONNECTION_SECURITY_MODES, + initial="starttls", + help_text=_("Connection security mechanism") + ) + + visibility_rules = { + "smtp_server_address": "include_connection_settings=True", + "smtp_server_port": "include_connection_settings=True", + "smtp_connection_security": "include_connection_settings=True", + "imap_server_address": "include_connection_settings=True", + "imap_server_port": "include_connection_settings=True", + "imap_connection_security": "include_connection_settings=True", + } + + @cached_property + def hostname(self): + """Return local hostname.""" + return sites_models.Site.objects.get_current().domain + + def __init__(self, *args, **kwargs): + """Set initial values.""" + super(ParametersForm, self).__init__(*args, **kwargs) + if not self.fields["webpanel_url"].initial: + url = "https://{}{}".format(self.hostname, settings.LOGIN_URL) + self.fields["webpanel_url"].initial = url + if not self.fields["smtp_server_address"].initial: + self.fields["smtp_server_address"].initial = self.hostname + if not self.fields["imap_server_address"].initial: + self.fields["imap_server_address"].initial = self.hostname + + +def load_settings(): + """Load app settings.""" + from modoboa.parameters import tools as param_tools + + param_tools.registry.add( + "global", ParametersForm, _("PDF credentials")) \ No newline at end of file diff --git a/modoboa/pdfcredentials/handlers.py b/modoboa/pdfcredentials/handlers.py new file mode 100644 index 000000000..448cea6bd --- /dev/null +++ b/modoboa/pdfcredentials/handlers.py @@ -0,0 +1,53 @@ +"""PDF credentials handlers.""" + +import os + +from django.urls import reverse +from django.db.models import signals +from django.dispatch import receiver +from django.utils.translation import ugettext as _ + +from modoboa.core import models as core_models +from modoboa.core import signals as core_signals +from modoboa.parameters import tools as param_tools + +from .documents import credentials +from .lib import init_storage_dir, delete_credentials, get_creds_filename + + +@receiver(core_signals.account_password_updated) +def password_updated(sender, account, password, created, **kwargs): + """Create or update document.""" + if not param_tools.get_global_parameter("enabled_pdfcredentials"): + return + generate_at_creation = param_tools.get_global_parameter( + "generate_at_creation") + if (generate_at_creation and not created) or account.is_superuser: + return + init_storage_dir() + credentials(account, password) + + +@receiver(signals.pre_delete, sender=core_models.User) +def account_deleted(sender, instance, **kwargs): + """Remove document.""" + delete_credentials(instance) + + +@receiver(core_signals.extra_account_actions) +def extra_account_actions(sender, account, **kwargs): + """Add link to download document.""" + if not param_tools.get_global_parameter("enabled_pdfcredentials"): + return [] + fname = get_creds_filename(account) + if not os.path.exists(fname): + return [] + return [{ + "name": "get_credentials", + "url": reverse("pdfcredentials:account_credentials", + args=[account.id]), + "img": "fa fa-download", + "title": _("Retrieve user's credentials as a PDF document") + }] + + diff --git a/modoboa/pdfcredentials/lib.py b/modoboa/pdfcredentials/lib.py new file mode 100644 index 000000000..2d9785bbf --- /dev/null +++ b/modoboa/pdfcredentials/lib.py @@ -0,0 +1,101 @@ +"""Internal library.""" + +from io import BytesIO +import os +import struct + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend + +from django.conf import settings +from django.utils.encoding import force_bytes, smart_bytes +from django.utils.translation import ugettext as _ + +from modoboa.lib.exceptions import InternalError +from modoboa.parameters import tools as param_tools + + +def init_storage_dir(): + """Create the directory whare documents will be stored.""" + storage_dir = param_tools.get_global_parameter("storage_dir") + if os.path.exists(storage_dir): + return + try: + os.mkdir(storage_dir) + except (OSError, IOError) as inst: + raise InternalError( + _("Failed to create the directory that will contain " + "PDF documents (%s)") % inst + ) + + +def get_creds_filename(account): + """Return the full path of a document.""" + storage_dir = param_tools.get_global_parameter("storage_dir") + return os.path.join(storage_dir, account.username + ".pdf") + + +def delete_credentials(account): + """Try to delete a local file.""" + fname = get_creds_filename(account) + if not os.path.exists(fname): + return + os.remove(fname) + + +def _get_cipher(iv): + """Return ready-to-user Cipher.""" + key = smart_bytes(settings.SECRET_KEY[:32]) + backend = default_backend() + return Cipher( + algorithms.AES(force_bytes(key)), + modes.CBC(iv), + backend=backend + ) + + +def crypt_and_save_to_file(content, filename, length, chunksize=64*512): + """Crypt content and save it to a file.""" + iv = os.urandom(16) + cipher = _get_cipher(iv) + encryptor = cipher.encryptor() + with open(filename, "wb") as fp: + fp.write(struct.pack(b", 2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-08-10 11:24+0000\n" +"Last-Translator: Jarda Tesar \n" +"Language-Team: Czech (Czech Republic) (http://www.transifex.com/tonio/modoboa/language/cs_CZ/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: cs_CZ\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" + +#: constants.py:7 +msgid "None" +msgstr "Žádné" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Informace osobního účtu" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Běží na Modoboa - Poštovní hosting zjednodušeně" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nVážený %s, tento dokument obsahuje přístupové údaje potřebné\npro pripojení do rozhraní Modoboa. Zapamatujte si je a smazte\ntento dokument hned, jakmile to bude možné.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Webový panel:" + +#: documents.py:78 +msgid "Username" +msgstr "Uživatelské jméno" + +#: documents.py:79 +msgid "Password" +msgstr "Heslo" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nZde si kdykoliv můžete prohlédnout své emaily, vytvořit filtry nebo spravovat Vaše kontakty.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Prosím změňte si své heslo!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "Nastavení PC/Tabletu/Smartphonu:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Adresa SMTP serveru" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Port SMTP serveru" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Zabezpečení SMTP připojení" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "Adresa IMAP serveru" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "Port IMAP serveru" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Zabezpečení IMAP připojení" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Použijte tato nastavení ve svém počítači, tabletu či telefonu." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Úložný prostor pro dokumenty" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Adresář pro uložení dokumentů" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Cesta k adresáři kde budou uloženy PDF dokumenty" + +#: forms.py:28 +msgid "Security options" +msgstr "Možnosti zabezpečení" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Smazat dokumenty po prvním stažení" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Automaticky smazat dokument ihned po prvním stažení cez toto rozhraní" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Vytvářet dokumenty pouze při vytváření účtu" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Vytvářet nové dokumenty pouze při vytvoření účtu. Je-li nastaveno na hodnotu ne, bude nový dokument vytvořen pokaždé, když dojde ke změne hesla." + +#: forms.py:49 +msgid "Customization options" +msgstr "Možnosti vlastních úprav" + +#: forms.py:52 +msgid "Title" +msgstr "Název" + +#: forms.py:54 +msgid "The document's title" +msgstr "Název dokumentu" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL web panelu" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL adresa Modoboa web panelu" + +#: forms.py:63 +msgid "Custom message" +msgstr "Vlastní zpráva" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Vlastní zpráva, která se zobrazí na konci dokumentu." + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Zahrnout nastavení mailového klienta" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Zahrnout vyžadovaná nastavení SMTP a IMAP připojení pri konfiguraci mailového klienta, tabletu či telefonu" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Adresa SMTP serveru (hostname nebo IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Port SMTP serveru" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Zabezpečení připojení" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Adresa IMAP serveru (hostname nebo IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Port IMAP serveru" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Získat přístupové údaje uživatele ako PDF dokument" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Vytváření adresáře pro uložení PDF dokumentů (%s) selhalo " + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "PDF přístupové údaje" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Vytvářet PDF dokumenty obsahující přístupové údaje uživatele" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Pro tohoto uživatele není k dispozici žáden dokument" diff --git a/modoboa/pdfcredentials/locale/de/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/de/LC_MESSAGES/django.po new file mode 100644 index 000000000..6c98d2145 --- /dev/null +++ b/modoboa/pdfcredentials/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,212 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Boris Andratzek , 2018 +# Christian Wiegand, 2017 +# controlcde, 2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-04-25 14:19+0000\n" +"Last-Translator: Boris Andratzek \n" +"Language-Team: German (http://www.transifex.com/tonio/modoboa/language/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "kein" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Persönliche Kontoinformationen" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Powered by Modoboa - Mail hosting made simple" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nLiebe/r %s, dieses Dokument enthält die Anmeldeinformationen, die Du benötigst um Dich mit Modoboa zu verbinden. Präge Dir bitte die Informationen ein und lösche anschliessend dieses Dokument.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Webanwendung:" + +#: documents.py:78 +msgid "Username" +msgstr "Benutzername" + +#: documents.py:79 +msgid "Password" +msgstr "Passwort" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nHier können Sie Ihre Emails online ansehen, Filter oder Kontakte erstellen.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Bitte ändern Sie Ihr Passwort!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "PC/Tablet/Smartphone Konfiguration:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "SMTP Serveradresse" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "SMTP Server-Port" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "SMTP-Verbindungssicherheit" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "IMAP Serveradresse" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "IMAP Server-Port" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "IMAP Verbindungssicherheit" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Verwenden Sie diese Einstellungen für Ihren Computer, Ihr Tablet oder Telefon" + +#: forms.py:20 +msgid "Documents storage" +msgstr "Dokumentenablage" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Speicherpfad für Dokumente" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Dateipfad in dem die PDF-Dokumente abgelegt werden" + +#: forms.py:28 +msgid "Security options" +msgstr "Sicherheitseinstellungen" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Lösche Dokumente nach dem ersten Download" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Löscht automatisch das Dokument, wenn es das erste Mal über dieses Oberfläche heruntergeladen wurde" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Erzeuge Dokumente nur wenn ein Konto angelegt wurde" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Erzeuge ein neues Dokument nur wenn ein neues Konto erstellt wurde. Wenn die Option auf Nein steht wird jedes Mal ein neues Dokument erstellt, wenn ein Passwort aktualisiert wurde." + +#: forms.py:49 +msgid "Customization options" +msgstr "Individuell angepasste Optionen" + +#: forms.py:52 +msgid "Title" +msgstr "Titel" + +#: forms.py:54 +msgid "The document's title" +msgstr "Titel des Dokuments" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL der Webanwendung" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL der Modoboa Webanwendung" + +#: forms.py:63 +msgid "Custom message" +msgstr "Eigener Nachrichtentext" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Eine eigene Nachricht, die am Ende des Dokuments angezeigt wird" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Verbindungseinstellungen für Mailprogramme einschließen" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Erforderliche SMTP und IMAP Einstellungen zur Konfiguration von Mailprogramme, Tablets oder Telefonen" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Adresse des SMTP-Servers (Hostname oder IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Port, auf dem der SMTP-Server erreicht werden kann" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Mechanismus zur Verbindungssicherung" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Adresse des IMAP-Servers (Hostname oder IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Port, auf dem der IMAP-Servier erreicht werden kann" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Erhalte die Anmeldeinformationen des Benutzer als ein PDF-Dokument" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Ein Fehler ist bei der Erstellung des Pfades, der die PDF Dokumente (%s) enthält, aufgetreten. " + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "PDF Anmeldeinformationen" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Erzeuge PDF-Dokumente welche die Benutzeranmeldeinformationen enthält" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Kein Dokument für diesen Benutzer verfügbar" diff --git a/modoboa/pdfcredentials/locale/el_GR/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/el_GR/LC_MESSAGES/django.po new file mode 100644 index 000000000..e1c5bd632 --- /dev/null +++ b/modoboa/pdfcredentials/locale/el_GR/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Giannis Kapetanakis , 2016 +# Kostas Moumouris , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2017-10-12 13:30+0000\n" +"Last-Translator: Kostas Moumouris \n" +"Language-Team: Greek (Greece) (http://www.transifex.com/tonio/modoboa/language/el_GR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: el_GR\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "Κανένας" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Προσωπικές πληροφορίες λογαριασμού" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Powered by Modoboa - Mail hosting made simple" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nΑγαπητέ/ή %s, αυτό το έγγραφο περιέχει τα διαπιστευτήρια\nπου χρειάζεστε για να συνδεθείτε με το Modoboa.\nΑποστηθίστε το περιεχόμενο και διαγράψτε το έγγραφο\nτο συντομότερο δυνατόν.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Web panel:" + +#: documents.py:78 +msgid "Username" +msgstr "Όνομα Χρήστη" + +#: documents.py:79 +msgid "Password" +msgstr "Κωδικός" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nΕδώ μπορείτε να προβάλετε τα ηλεκτρονικά σας μηνύματα σε πραγματικό χρόνο, να δημιουργήσετε φίλτρα ή να διαχειριστείτε\n τις επαφές σας.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Παρακαλώ αλλάξτε το κωδικό σας!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "Ρυθμίσεις: PC/Tablet/Smartphone " + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Διεύθυνση SMTP Server" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Θύρα SMTP server" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Ασφάλεια σύνδεσης SMTP" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "Διεύθυνση IMAP server" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "θύρα IMAP Server" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Ασφάλεια σύνδεσης IMAP" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Χρησιμοποιήστε αυτές τις ρυθμίσεις για τον υπολογιστή, το tablet ή το τηλέφωνό σας." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Αποθηκευτικός χώρος εγγράφων" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Κατάλογος αρχείων για την αποθήκευση εγγράφων" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Κατάλογος αρχείων για την αποθήκευση PDF εγγράφων" + +#: forms.py:28 +msgid "Security options" +msgstr "Ρυθμίσεις Ασφαλείας" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Διαγραφή εγγράφων μετά την πρώτη λήψη" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Αυτόματη διαγραφή εγγράφου μετά την πρώτη λήψη από αυτήν την διεπαφή" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Δημιουργία εγγράφων μόνο κατά την δημιουργία λογαριασμού" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Δημιουργία νέου εγγράφου μόνο κατά την δημιουργία νέου λογαριασμού. Εάν επιλέξετε όχι, ένα νέο έγγραφο θα δημιουργείται κάθε φορά που ο κωδικός χρήστη ενημερώνεται." + +#: forms.py:49 +msgid "Customization options" +msgstr "Επιλογές προσαρμογής" + +#: forms.py:52 +msgid "Title" +msgstr "Τίτλος" + +#: forms.py:54 +msgid "The document's title" +msgstr "Ο τίτλος του εγγράφου" + +#: forms.py:58 +msgid "Web panel url" +msgstr "Διεύθυνση Web panel" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL του Modoboa web panel" + +#: forms.py:63 +msgid "Custom message" +msgstr "Προσαρμοσμένο μήνυμα" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Ένα προσαρμοσμένο μήνυμα που θα εμφανιστεί στο τέλος των εγγράφων" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Συμπεριλάβε τις ρυθμίσεις σύνδεσης προγράμματος αλληλογραφίας" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Συμπεριέλαβε της απαιτούμενες ρυθμίσης SMTP και IMAP για διασύνδεση με προγράμματα emai, τάμπλετ και τηλέφωνα" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Διεύθυνση από τον SMTP server (hostname ή IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Οι θύρες που ακούει ό SMTP Server" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Μηχανισμός ασφαλείας σύνδεσης" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Διεύθυνση του IMAP server (hostname ή IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Η θύρα που ακούει ο IMAP Server" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Ανάκτηση διαπιστευτηρίων χρήστη σε αρχείο PDF" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Σφάλμα στην δημιουργία του καταλόγου αρχείων για την αποθήκευση PDF εγγράφων (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "Διαπιστευτήρια PDF" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Δημιουργία PDF εγγράφων που να περιέχουν τα διαπιστευτήρια χρηστών" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Δεν υπάρχουν διαθέσιμα έγγραφα για αυτόν τον χρήστη" diff --git a/modoboa/pdfcredentials/locale/en/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/en/LC_MESSAGES/django.po new file mode 100644 index 000000000..7405024a2 --- /dev/null +++ b/modoboa/pdfcredentials/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,209 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: constants.py:7 +msgid "None" +msgstr "" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "" + +#: documents.py:75 +msgid "Web panel:" +msgstr "" + +#: documents.py:78 +msgid "Username" +msgstr "" + +#: documents.py:79 +msgid "Password" +msgstr "" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "" + +#: forms.py:20 +msgid "Documents storage" +msgstr "" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "" + +#: forms.py:28 +msgid "Security options" +msgstr "" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "" + +#: forms.py:49 +msgid "Customization options" +msgstr "" + +#: forms.py:52 +msgid "Title" +msgstr "" + +#: forms.py:54 +msgid "The document's title" +msgstr "" + +#: forms.py:58 +msgid "Web panel url" +msgstr "" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "" + +#: forms.py:63 +msgid "Custom message" +msgstr "" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "" + +#: views.py:30 +msgid "No document available for this user" +msgstr "" diff --git a/modoboa/pdfcredentials/locale/fr/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 000000000..01b723e42 --- /dev/null +++ b/modoboa/pdfcredentials/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,210 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Antoine Nguyen , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2017-10-16 18:51+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: French (http://www.transifex.com/tonio/modoboa/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "Aucune" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Informations de compte personnel" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Propulsé par Modoboa - L'hébergement mail simplifié" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nCher %s, ce document contient les identifiants dont vous aurez\nbesoin pour vous connecter à Modoboa. Apprenez les puis détruisez\nce document le plus rapidement possible.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Console web :" + +#: documents.py:78 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: documents.py:79 +msgid "Password" +msgstr "Mot de passe" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nD'ici vous pouvez consulter vos emails en ligne n'importe quand, créer des filtres ou gérer vos contacts.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Merci de changer votre mot de passe !" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "Configuration PC/Tablette/Smartphone :" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Adresse du serveur SMTP" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Port du serveur SMTP" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Sécurité de la connexion SMTP" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "Adresse du serveur IMAP" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "Port du serveur IMAP" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Sécurité de la connexion IMAP" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Utilisez ces paramètres pour votre ordinateur, votre tablette ou votre téléphone." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Stockage des documents" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Répertoire dans lequel les documents sont sauvegardés" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Chemin d'accès vers le répertoire ou seront stockés les documents" + +#: forms.py:28 +msgid "Security options" +msgstr "Options de sécurité" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Supprimer les documents après le premier téléchargement" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Supprimer automatiquement un document juste après son premier téléchargement depuis cette interface" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Générer les documents à la création des comptes uniquement" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Génère un nouveau document uniquement lors de la création d'un compte. Si défini à non, un nouveau document sera généré chaque fois qu'un mot de passe est changé." + +#: forms.py:49 +msgid "Customization options" +msgstr "Options de personnalisation" + +#: forms.py:52 +msgid "Title" +msgstr "Titre" + +#: forms.py:54 +msgid "The document's title" +msgstr "Le titre du document" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL de la console web" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL de la console web Modoboa" + +#: forms.py:63 +msgid "Custom message" +msgstr "Message personnalisé" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Un message personnalisé qui apparaîtra en bas des documents." + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Inclure les paramètres de connexion pour client mail" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Inclure les paramètres de connexion nécessaires pour configurer un client mail, une tablette ou un téléphone" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Adresse du serveur SMTP (nom d'hôte ou IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Port d'écoute du serveur SMTP" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Mécanisme de sécurisation de la connexion" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Adresse du serveur IMAP (nom d'hôte ou IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Port d'écoute du serveur IMAP" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Récupérer les identifiant de l'utilisateur sous forme d'un document PDF" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Echec lors de la création du répertoire qui contiendra les documents PDF (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "Identifiants en PDF" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Génère des documents PDF contenant les identifiants des utilisateurs" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Aucun document disponible pour cet utilisateur" diff --git a/modoboa/pdfcredentials/locale/it/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/it/LC_MESSAGES/django.po new file mode 100644 index 000000000..fb1878d7e --- /dev/null +++ b/modoboa/pdfcredentials/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# marcotrevisan , 2018 +# Rocco , 2015 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-11-02 15:15+0000\n" +"Last-Translator: marcotrevisan \n" +"Language-Team: Italian (http://www.transifex.com/tonio/modoboa/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "Nessun valore" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Informazioni Personali" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Powered by Modoboa - Mail hosting made simple" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nGentile %s, questo documento contiene le credenziali di accesso \nper accedere a Modoboa. Leggilo e elimina\n il documento il prima possibile.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Pannello web:" + +#: documents.py:78 +msgid "Username" +msgstr "Nome Utente" + +#: documents.py:79 +msgid "Password" +msgstr "Password" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nQui puoi sempre visualizzare le tue email onlime, creare filtri\no gestire i tuoi contatti.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Per favore cambia la tua password!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "Configurazione PC/Tablet/Smartphone:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Indirizzo del server SMTP" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Porta del server SMTP" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Sicurezza della connessione al server SMTP" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "indirizzo del server IMAP" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "Porta del server IMAP" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Sicurezza della connessione al server IMAP" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Utilizza quelle impostazioni per il tuo computer, tablet o telefono." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Memorizzazione Documenti" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Cartella nella quale salvare i documenti" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Indirizzo della cartella dove i documenti PDF saranno salvati" + +#: forms.py:28 +msgid "Security options" +msgstr "Opzioni di sicurezza" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Cancella i documenti dopo averli scaricati" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Elimina automaticamente il documento dopo che è stato scaricato da questa interfaccia" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Genera documenti solo dopo la creazione dell'account" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Generare un nuovo documento solo quando si crea un nuovo account. Se impostato su no , un nuovo documento verrà creato ogni volta che la password viene aggiornata." + +#: forms.py:49 +msgid "Customization options" +msgstr "Opzioni di personalizzazione" + +#: forms.py:52 +msgid "Title" +msgstr "Titolo" + +#: forms.py:54 +msgid "The document's title" +msgstr "Il titolo del documento" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL del pannello di controllo web" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL del pannello di controllo web di Modoboa" + +#: forms.py:63 +msgid "Custom message" +msgstr "Messaggio personalizzato" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Un messaggio personalizzato comparirà alla fine dei documenti" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Includi le impostazioni di connessione del client di posta" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Includi le informazioni di connessione SMTP e IMAP richieste per configurare un client di posta, un tablet o telefono" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Indirizzo del server SMTP (nome host o indirizzo IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Porta di ascolto del server SMTP" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Meccanismo di sicurezza della connessione" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Indirizzo del server IMAP (nome host o indirizzo IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Porta di ascolto del server IMAP" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Recuperare le credenziali dell'utente come documento PDF" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Impossibile creare la cartella che conterrà i documenti PDF (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "Credenziali PDF" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Genera i documenti PDF con le credenziali dell'utente" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Non ci sono documenti disponibili er questo utente" diff --git a/modoboa/pdfcredentials/locale/ja_JP/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/ja_JP/LC_MESSAGES/django.po new file mode 100644 index 000000000..2ec0ba19a --- /dev/null +++ b/modoboa/pdfcredentials/locale/ja_JP/LC_MESSAGES/django.po @@ -0,0 +1,210 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# cwatanab , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2017-09-27 10:02+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: Japanese (Japan) (http://www.transifex.com/tonio/modoboa/language/ja_JP/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ja_JP\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: constants.py:7 +msgid "None" +msgstr "" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "個人アカウント情報" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Powered by Modoboa - メールホスティングをシンプルに" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\n%s さん\n\nこのドキュメントには、Modoboa へ接続するための必要な認証情報が書かれています。\n内容を理解したら、すみやかに廃棄してください。\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "" + +#: documents.py:78 +msgid "Username" +msgstr "ユーザ名" + +#: documents.py:79 +msgid "Password" +msgstr "パスワード" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "" + +#: forms.py:20 +msgid "Documents storage" +msgstr "ドキュメントストレージ" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "ドキュメントを保存するためのディレクトリ" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "PDF ドキュメントを保存するためのディレクトリのパス" + +#: forms.py:28 +msgid "Security options" +msgstr "セキュリティオプション" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "最初のダウンロードの後にドキュメントを削除する" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "このインターフェースから最初にダウンロードされたあとにドキュメントを自動的に削除する" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "アカウントを作成するときだけドキュメントを生成する" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "アカウントの作成されたときだけ、新しいドキュメントを生成します。\nもし「いいえ」設定すれば、新しい文書は、パスワードが更新されるたびに作成されます。" + +#: forms.py:49 +msgid "Customization options" +msgstr "" + +#: forms.py:52 +msgid "Title" +msgstr "" + +#: forms.py:54 +msgid "The document's title" +msgstr "" + +#: forms.py:58 +msgid "Web panel url" +msgstr "" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "" + +#: forms.py:63 +msgid "Custom message" +msgstr "" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "PDF ドキュメントとしてユーザー認証情報を取得します" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "PDFドキュメントを格納するディレクトリの作成に失敗しました (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "PDF 認証情報" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "ユーザーの認証情報を書かれた PDF ドキュメントを生成" + +#: views.py:30 +msgid "No document available for this user" +msgstr "このユーザーにはドキュメンがありません" diff --git a/modoboa/pdfcredentials/locale/nl_NL/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/nl_NL/LC_MESSAGES/django.po new file mode 100644 index 000000000..0a828652a --- /dev/null +++ b/modoboa/pdfcredentials/locale/nl_NL/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# R Kloosterman , 2016 +# Tuxis Internet Engineering V.O.F. , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-01-31 08:43+0000\n" +"Last-Translator: Tuxis Internet Engineering V.O.F. \n" +"Language-Team: Dutch (Netherlands) (http://www.transifex.com/tonio/modoboa/language/nl_NL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl_NL\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Persoonlijke account informatie" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Aangeboden door Modoboa - Mail hosting made simple" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nBeste %s, dit document bevat alle gegevens dat u nodig\nheeft voor het gebruiken van Modoboa. Onthoud deze gegevens\nen verwijder dit document zo snel mogelijk.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Webpaneel:" + +#: documents.py:78 +msgid "Username" +msgstr "Gebruikersnaam" + +#: documents.py:79 +msgid "Password" +msgstr "Wachtwoord" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nHier kunt u uw e-mails online bekijken, filters aanmaken, of contactpersonen beheren.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Wijzig uw wachtwoord aub!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "PC/Tablet/Smartphone configuratie:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "SMTP serveradres" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "SMTP server poort" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "SMTP verbinding beveiliging" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "IMAP serveradres" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "IMAP server poort" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "IMAP verbinding beveiliging" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Gebruik deze instellingen voor uw computer, tablet of telefoon." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Document opslag" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Map voor het opslaan van de documenten" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Pad naar de map waar de PDF documenten worden opgeslagen" + +#: forms.py:28 +msgid "Security options" +msgstr "Beveiligings-opties" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Documenten verwijderen na eerste download" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Verwijder een document automatisch nadat het eenmalig is gedownload van paneel" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Genereer een document enkel bij het aanmaken van een account" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Genereer een document enkel wanneer een account is nieuw aangemaakt. Indien 'nee' is geselecteerd zal bij iedere wachtwoord wijziging een nieuw document worden gemaakt. " + +#: forms.py:49 +msgid "Customization options" +msgstr "Personaliseringsopties" + +#: forms.py:52 +msgid "Title" +msgstr "Titel" + +#: forms.py:54 +msgid "The document's title" +msgstr "Titel van het document" + +#: forms.py:58 +msgid "Web panel url" +msgstr "Webpaneel URL" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL van het Modoboa webpaneel" + +#: forms.py:63 +msgid "Custom message" +msgstr "Aangepast bericht" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Een aangepast bericht dat aan het einde van de documenten komt te staan" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Vraag de gebruikers gegevens op als een PDF document" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Het aanmaken van de map voor de PDF documenten is mislukt (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "PDF gegevens" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Genereer PDF document met de gegevens voor de gebruiker" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Geen document beschikbaar voor deze gebruiker" diff --git a/modoboa/pdfcredentials/locale/pl_PL/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 000000000..20c3f866e --- /dev/null +++ b/modoboa/pdfcredentials/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# sin88 , 2016 +# Tomasz Proć , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2017-12-02 10:36+0000\n" +"Last-Translator: Tomasz Proć \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/tonio/modoboa/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" + +#: constants.py:7 +msgid "None" +msgstr "Brak" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Informacje o koncie osobistym" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Powered by Modoboa - Mail hosting made simple" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nDrogi użytkowniku %s, ten dokument zawiera dane uwierzytelniające które będzeisz potrzebował to połączenia się. Zapoznaj się z nim i usuń go najszybciej jak to możliwe.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Panel web:" + +#: documents.py:78 +msgid "Username" +msgstr "Nazwa użytkownika" + +#: documents.py:79 +msgid "Password" +msgstr "Hasło" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nPod tym adresem możesz online przeczytać swoje wiadomości, stworzyć filtry lub zarządzać\n listą kontaktów.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Proszę zmienić swoje hasło!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "konfiguracją PC/Tabletu/Smartfonu:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Adres serwera SMTP" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Port serwera SMTP" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Zabezpieczenia połączenia SMTP" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "Adres serwera IMAP" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "Port serwera IMAP" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Zabezpieczenie połączenia IMAP" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Użyj tych ustawień w swoim komputerze, tablecie lub telefonie." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Magazyn dokumentów" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Lokalizacja gdzie zapisywać dokumenty" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Ścieżka do lokalizacji, gdzie będą zapisywane PDF'y" + +#: forms.py:28 +msgid "Security options" +msgstr "Opcje bezpieczeństwa" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Usuwaj dokumenty po ich pierwszym pobraniu" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Automatycznie usuwaj dokumenty jak tylko zostaną pobranie przez ten interfejs" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Generuj dokumenty tylko podczas zakładania konta" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Generuj nowy dokument tylko wtedy, kiedy nowe konto jest zakładane. Jeśli ustawione na nie, to nowy dokument będzie tworzony przy każdej zmianie hasła." + +#: forms.py:49 +msgid "Customization options" +msgstr "Opcje dostosowania" + +#: forms.py:52 +msgid "Title" +msgstr "Tytuł" + +#: forms.py:54 +msgid "The document's title" +msgstr "Tytuł dokumentu" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL panelu Web" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL panelu Modoboa" + +#: forms.py:63 +msgid "Custom message" +msgstr "Własna wiadomość" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Dodatkowa wiadomość która pojawi się na końcu dokumentu." + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Załącz ustawienia połączenia dla programu pocztowego klienta" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Załącz wymagane do konfiguracji, informacje o połączeniu SMTP i IMAP " + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Adres serwera SMTP (nazwa hosta lub IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Port nasłuchujący serwera SMTP" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Mechanizm zabezpieczenia połączenia" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Adres serwera IMAP (nazwa hosta lub IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Port nasłuchujący serwera IMAP" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Pobieranie danych uwierzytelniających użytkownika w formacie PDF" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Nie można utworzyć katalogu do przechowywania PDF'ów (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "Poświadczenia PDF" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Generuj dokumenty PDF zawierające dane uwierzytelniające użytkowników" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Brak dokumentu dla tego użytkownika" diff --git a/modoboa/pdfcredentials/locale/pt_BR/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 000000000..41a9b5b47 --- /dev/null +++ b/modoboa/pdfcredentials/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rafael Barretto , 2015 +# Rafael Barretto , 2017-2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-09-25 03:25+0000\n" +"Last-Translator: Rafael Barretto \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/tonio/modoboa/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "Nenhuma" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Informações da conta pessoal" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Desenvolvido por Modoboa - Hospedagem de email de forma simples" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nCaro(a) %s, este documento contem as credenciais que você irá precisar\npara conectar ao Modoboa. Verifique o conteúdo e destrua\neste documento assim que possível.\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Painel web:" + +#: documents.py:78 +msgid "Username" +msgstr "Nome de usuário" + +#: documents.py:79 +msgid "Password" +msgstr "Senha" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nAqui você poderá ver seus emails a qualquer momento online, crie filtros ou gerencie \nseus contatos.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Por favor mude sua senha!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "Configuração para PC/Tablet/Smartphone:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "Endereço do servidor SMTP" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "Porta do servidor SMTP" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "Conexão segura SMTP" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "Endereço do servidor IMAP" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "Porta do servidor IMAP" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "Conexão segura IMAP" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Use essas configurações para seu computador, tablet ou telefone." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Armazenamento de documentos" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Diretório para salvar documentos" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Caminho para o diretório onde os arquivos PDF serão salvos" + +#: forms.py:28 +msgid "Security options" +msgstr "Opções de segurança" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Deletar documentos após o primeiro download" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Excluir automaticamente um documento logo após a sua primeira transferência a partir desta interface" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Apenas gerar documentos na criação da conta" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Gerar um novo documento apenas quando uma nova conta é criada. Se definido como não, um novo documento será criado cada vez que uma senha é atualizada." + +#: forms.py:49 +msgid "Customization options" +msgstr "Opções de customização" + +#: forms.py:52 +msgid "Title" +msgstr "Título" + +#: forms.py:54 +msgid "The document's title" +msgstr "Título do documento" + +#: forms.py:58 +msgid "Web panel url" +msgstr "URL do painel web" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL do painel web do Modoboa" + +#: forms.py:63 +msgid "Custom message" +msgstr "Mensagem personalizada" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Uma mensagem personalizada irá aparecer no fim do documento" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Incluir configurações de conexão do cliente de email" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Incluir informações de conexão SMTP e IMAP necessárias para configurar um cliente de e-mail, um tablet ou um telefone" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Endereço do servidor SMTP (hostname ou IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Mecanismo de conexão segura" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Recuperar credenciais do usuário como um documento PDF" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Falha ao criar o diretório que conterá documentos PDF (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "Credenciais PDF" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Gerar documentos em PDF contendo as credenciais dos usuários" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Nenhum documento disponível para esse usuário" diff --git a/modoboa/pdfcredentials/locale/pt_PT/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/pt_PT/LC_MESSAGES/django.po new file mode 100644 index 000000000..feb9e5fbd --- /dev/null +++ b/modoboa/pdfcredentials/locale/pt_PT/LC_MESSAGES/django.po @@ -0,0 +1,209 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2017-09-27 10:02+0000\n" +"Last-Translator: Antoine Nguyen \n" +"Language-Team: Portuguese (Portugal) (http://www.transifex.com/tonio/modoboa/language/pt_PT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_PT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "" + +#: documents.py:75 +msgid "Web panel:" +msgstr "" + +#: documents.py:78 +msgid "Username" +msgstr "" + +#: documents.py:79 +msgid "Password" +msgstr "" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "" + +#: forms.py:20 +msgid "Documents storage" +msgstr "" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "" + +#: forms.py:28 +msgid "Security options" +msgstr "" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "" + +#: forms.py:49 +msgid "Customization options" +msgstr "" + +#: forms.py:52 +msgid "Title" +msgstr "" + +#: forms.py:54 +msgid "The document's title" +msgstr "" + +#: forms.py:58 +msgid "Web panel url" +msgstr "" + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "" + +#: forms.py:63 +msgid "Custom message" +msgstr "" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "" + +#: views.py:30 +msgid "No document available for this user" +msgstr "" diff --git a/modoboa/pdfcredentials/locale/sv/LC_MESSAGES/django.po b/modoboa/pdfcredentials/locale/sv/LC_MESSAGES/django.po new file mode 100644 index 000000000..804ce42ff --- /dev/null +++ b/modoboa/pdfcredentials/locale/sv/LC_MESSAGES/django.po @@ -0,0 +1,211 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Olle Gustafsson , 2016,2018 +# Tomas Olsson , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Modoboa\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-09-27 12:01+0200\n" +"PO-Revision-Date: 2018-08-14 08:40+0000\n" +"Last-Translator: Olle Gustafsson \n" +"Language-Team: Swedish (http://www.transifex.com/tonio/modoboa/language/sv/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: sv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: constants.py:7 +msgid "None" +msgstr "Ingen" + +#: documents.py:52 forms.py:53 +msgid "Personal account information" +msgstr "Personlig kontoinformation" + +#: documents.py:55 +msgid "Powered by Modoboa - Mail hosting made simple" +msgstr "Drivs av Modoboa - Mail hosting gjort enkelt" + +#: documents.py:69 +#, python-format +msgid "" +"\n" +"Dear %s, this document contains the credentials you will need\n" +"to connect to Modoboa. Learn the content and destroy\n" +"the document as soon as possible.\n" +msgstr "\nKära %s, detta dokument innehåller de kontouppgifter du behöver för att ansluta till Modoboa. Lär dig innehållet i detta dokument och förstör detta dokument så snart som möjligt\n" + +#: documents.py:75 +msgid "Web panel:" +msgstr "Webbpanel" + +#: documents.py:78 +msgid "Username" +msgstr "Användarnamn" + +#: documents.py:79 +msgid "Password" +msgstr "Lösenord" + +#: documents.py:89 +msgid "" +"\n" +"Here you can view your emails anytime online, create filters or manage\n" +"your contacts.\n" +msgstr "\nHär kan du se dina e-postmeddelanden när som helst online, skapa filter eller hantera\ndina kontakter.\n" + +#: documents.py:96 +msgid "Please change your password!" +msgstr "Vänligen ändra ditt lösenord!" + +#: documents.py:102 +msgid "PC/Tablet/Smartphone configuration:" +msgstr "PC/Tablet/Smartphone inställningar:" + +#: documents.py:105 forms.py:79 +msgid "SMTP server address" +msgstr "SMTP server adress" + +#: documents.py:106 forms.py:84 +msgid "SMTP server port" +msgstr "SMTP server port" + +#: documents.py:107 forms.py:90 +msgid "SMTP connection security" +msgstr "SMTP anslutningssäkerhet" + +#: documents.py:108 forms.py:97 +msgid "IMAP server address" +msgstr "IMAP server adress" + +#: documents.py:109 forms.py:102 +msgid "IMAP server port" +msgstr "IMAP server port" + +#: documents.py:110 forms.py:108 +msgid "IMAP connection security" +msgstr "IMAP anslutningssäkerhet" + +#: documents.py:120 +msgid "Use those settings for your computer, tablet or phone." +msgstr "Använd dessa inställningar för din dator, surfplatta eller telefon." + +#: forms.py:20 +msgid "Documents storage" +msgstr "Dokumentlagring" + +#: forms.py:23 +msgid "Directory to save documents into" +msgstr "Katalog att spara dokument till" + +#: forms.py:25 +msgid "Path to a directory where PDF documents will be saved" +msgstr "Sökväg till en katalog där PDF dokument kommer sparas" + +#: forms.py:28 +msgid "Security options" +msgstr "Säkerhetsinställningar" + +#: forms.py:31 +msgid "Delete documents after the first download" +msgstr "Radera dokument efter första nedladdning" + +#: forms.py:34 +msgid "" +"Automatically delete a document just after its first download from this " +"interface" +msgstr "Radera dokument automatiskt efter första nedladdningen" + +#: forms.py:40 +msgid "Generate documents only at account creation" +msgstr "Generera dokument endast vid kontoskapande" + +#: forms.py:43 +msgid "" +"Generate a new document only when a new account is created. If set to no, a " +"new document will be created each time a password is updated." +msgstr "Generera bara nya dokument när ett nytt konto skapas. Om detta sätts till nej, kommer ett nytt dokument skapas varje gång ett lösenord uppdateras." + +#: forms.py:49 +msgid "Customization options" +msgstr "Anpassningsalternativ" + +#: forms.py:52 +msgid "Title" +msgstr "Titel" + +#: forms.py:54 +msgid "The document's title" +msgstr "Dokumentets titel" + +#: forms.py:58 +msgid "Web panel url" +msgstr "Webbpanels länk " + +#: forms.py:59 +msgid "URL of the Modoboa web panel" +msgstr "URL till Modoboa webbpanel" + +#: forms.py:63 +msgid "Custom message" +msgstr "Anpassat meddelande" + +#: forms.py:65 +msgid "A custom message that will appear at the end of documents" +msgstr "Ett anpassat meddelande som kommer visas i slutet på dokumentet" + +#: forms.py:70 +msgid "Include mail client connection settings" +msgstr "Inkludera e-post klient inställningar" + +#: forms.py:73 +msgid "" +"Include required SMTP and IMAP connection information to configure a mail " +"client, a tablet or a phone" +msgstr "Inkludera SMTP och IMAP anslutningsinformation för att konfigurera en e-postklient, surfplatta eller telefon" + +#: forms.py:80 +msgid "Address of the SMTP server (hostname or IP)" +msgstr "Adress till SMTP servern (värdnamn eller IP)" + +#: forms.py:86 +msgid "Listening port of the SMTP server" +msgstr "Lyssningsport för SMTP servern" + +#: forms.py:93 forms.py:111 +msgid "Connection security mechanism" +msgstr "Anslutningssäkerhetsmekanism" + +#: forms.py:98 +msgid "Address of the IMAP server (hostname or IP)" +msgstr "Adress till IMAP servern (värdnamn eller IP)" + +#: forms.py:104 +msgid "Listening port of the IMAP server" +msgstr "Lyssningsport för IMAP servern" + +#: handlers.py:46 +msgid "Retrieve user's credentials as a PDF document" +msgstr "Hämta användares kontouppgifter som ett PDF dokument" + +#: lib.py:29 +#, python-format +msgid "Failed to create the directory that will contain PDF documents (%s)" +msgstr "Kunde inte skapa katalogen som innehåller PDF dokumenten (%s)" + +#: modo_extension.py:16 modo_extension.py:24 +msgid "PDF credentials" +msgstr "PDF kontouppgifter" + +#: modo_extension.py:19 +msgid "Generate PDF documents containing users' credentials" +msgstr "Generera PDF dokument innehållande användares kontouppgifter" + +#: views.py:30 +msgid "No document available for this user" +msgstr "Inga dokument tillgängliga för den här användaren" diff --git a/modoboa/pdfcredentials/models.py b/modoboa/pdfcredentials/models.py new file mode 100644 index 000000000..71a836239 --- /dev/null +++ b/modoboa/pdfcredentials/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/modoboa/pdfcredentials/rfc6266.py b/modoboa/pdfcredentials/rfc6266.py new file mode 100644 index 000000000..c0dacb2d1 --- /dev/null +++ b/modoboa/pdfcredentials/rfc6266.py @@ -0,0 +1,109 @@ +""" +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/tests.py b/modoboa/pdfcredentials/tests.py new file mode 100644 index 000000000..885528ffa --- /dev/null +++ b/modoboa/pdfcredentials/tests.py @@ -0,0 +1,147 @@ +"""PDF credentials tests.""" + +import os +import shutil +import tempfile + +from django.test import override_settings +from django.urls import reverse + +from modoboa.admin import factories as admin_factories +from modoboa.core import models as core_models + +from modoboa.lib.tests import ModoTestCase + + +class EventsTestCase(ModoTestCase): + """Test event handlers.""" + + @classmethod + def setUpTestData(cls): + """Create some data.""" + super(EventsTestCase, cls).setUpTestData() + admin_factories.DomainFactory(name="test.com") + + def setUp(self): + """Create temp. directory to store files.""" + super(EventsTestCase, self).setUp() + self.workdir = tempfile.mkdtemp() + self.set_global_parameter("storage_dir", self.workdir) + + def tearDown(self): + """Reset test env.""" + shutil.rmtree(self.workdir) + + def _create_account(self, username, expected_status=200): + """Create a test account.""" + values = { + "username": username, + "first_name": "Tester", "last_name": "Toto", + "role": "SimpleUsers", "quota_act": True, + "is_active": True, "email": username, + "random_password": True, "stepid": 2 + } + response = self.client.post( + reverse("admin:account_add"), values, + HTTP_X_REQUESTED_WITH="XMLHttpRequest") + self.assertEqual(response.status_code, expected_status) + return values + + def test_password_updated(self): + """Check that document is generated at account creation/update.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + + # Check if link is present in listing page + response = self.ajax_get(reverse("admin:_identity_list")) + self.assertIn('name="get_credentials"', response["rows"]) + + # Try to download the file + response = self.client.get( + reverse("pdfcredentials:account_credentials", + args=[account.pk])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "application/pdf") + + # File have been deleted? + self.assertFalse(os.path.exists(fname)) + + # Try to download a second time + response = self.client.get( + reverse("pdfcredentials:account_credentials", + args=[account.pk])) + self.assertContains(response, "No document available for this user") + + # Update account + values.update({"language": "en"}) + self.ajax_post( + reverse("admin:account_change", args=[account.pk]), values + ) + self.assertFalse(os.path.exists(fname)) + + self.set_global_parameter("generate_at_creation", False) + self.ajax_post( + reverse("admin:account_change", args=[account.pk]), values + ) + self.assertTrue(os.path.exists(fname)) + + def test_with_connection_settings(self): + """Add connection settings to documents.""" + self.set_global_parameter("include_connection_settings", True) + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_with_custom_message(self): + """Add custom message to documents.""" + self.set_global_parameter("custom_message", "This is a test message.") + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_account_delete(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + self.ajax_post( + reverse("admin:account_delete", args=[account.pk]), {} + ) + self.assertFalse(os.path.exists(fname)) + + @override_settings(MODOBOA_LOGO="modoboa.png") + def test_with_custom_logo(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + + def test_download_and_delete_account(self): + """Check that document is deleted with account.""" + values = self._create_account("leon@test.com") + fname = os.path.join(self.workdir, "{}.pdf".format(values["username"])) + self.assertTrue(os.path.exists(fname)) + account = core_models.User.objects.get(username=values["username"]) + + # Try to download the file + response = self.client.get( + reverse("pdfcredentials:account_credentials", + args=[account.pk])) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "application/pdf") + + # File have been deleted? + self.assertFalse(os.path.exists(fname)) + + # Delete account + self.ajax_post( + reverse("admin:account_delete", args=[account.pk]), {} + ) + + def test_storage_dir_creation(self): + """Test storage directory creation.""" + self.set_global_parameter("storage_dir", "/nonexistentdir") + self._create_account("leon@test.com", expected_status=500) diff --git a/modoboa/pdfcredentials/urls.py b/modoboa/pdfcredentials/urls.py new file mode 100644 index 000000000..9a13a5b3c --- /dev/null +++ b/modoboa/pdfcredentials/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +app_name = "pdfcredentials" + +urlpatterns = [ + path('credentials//', views.get_account_credentials, + name="account_credentials"), +] diff --git a/modoboa/pdfcredentials/views.py b/modoboa/pdfcredentials/views.py new file mode 100644 index 000000000..584675db7 --- /dev/null +++ b/modoboa/pdfcredentials/views.py @@ -0,0 +1,37 @@ +"""modoboa.pdfcredentials views.""" + +import os + +from django.http import HttpResponse +from django.utils.translation import ugettext as _ + +from django.contrib.auth.decorators import ( + login_required, permission_required +) + +from modoboa.core.models import User +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 +@permission_required("core.add_user") +def get_account_credentials(request, accountid): + """View to download a document.""" + account = User.objects.get(pk=accountid) + if not request.user.can_access(account): + raise PermDeniedException() + fname = get_creds_filename(account) + if not os.path.exists(fname): + raise ModoboaException(_("No document available for this user")) + content = decrypt_file(fname) + if param_tools.get_global_parameter("delete_first_dl"): + os.remove(fname) + resp = HttpResponse(content) + resp["Content-Type"] = "application/pdf" + resp["Content-Length"] = len(content) + resp["Content-Disposition"] = build_header(os.path.basename(fname)) + return resp diff --git a/modoboa/urls.py b/modoboa/urls.py index 159d2583e..75ce14626 100644 --- a/modoboa/urls.py +++ b/modoboa/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path('', include("modoboa.core.urls")), path('admin/', include("modoboa.admin.urls")), path('dnstools/', include("modoboa.dnstools.urls")), + path('pdfcredentials', include("modoboa.pdfcredentials.urls")), path('stats/', include("modoboa.maillog.urls")), path('user/forward/', user_views.forward, name="user_forward"), diff --git a/modoboa/urls_api_v2.py b/modoboa/urls_api_v2.py index b6376b088..06ee56864 100644 --- a/modoboa/urls_api_v2.py +++ b/modoboa/urls_api_v2.py @@ -15,6 +15,7 @@ urlpatterns = [ path('', include("modoboa.dnstools.api.v2.urls")), path('', include("modoboa.maillog.api.v2.urls")), path('', include("modoboa.transport.api.v2.urls")), + path('', include("modoboa.pdfcredentials.api.v2.urls")), ] urlpatterns += exts_pool.get_urls(category="api") diff --git a/requirements.txt b/requirements.txt index 4e4c4de50..db8e88ad5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,3 +37,6 @@ qrcode aiosmtplib aioredis==2.0.1 + +# PDF credentials +reportlab \ No newline at end of file diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 4b74c587a..46b5699fe 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -89,6 +89,7 @@ MODOBOA_APPS = ( 'modoboa.dnstools', 'modoboa.policyd', 'modoboa.maillog', + 'modoboa.pdfcredentials', # Modoboa extensions here. )