Compare commits

...

10 Commits

Author SHA1 Message Date
Raphael Michel
6d834762c4 Bump to 2023.7.0 2023-07-28 09:29:07 +02:00
Raphael Michel
4f1e9a31c6 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5400 of 5400 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-07-27 14:17:58 +02:00
Raphael Michel
8ed3911dfb Translations: Update German
Currently translated at 100.0% (5400 of 5400 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-07-27 14:17:58 +02:00
Raphael Michel
4562879cb2 Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-07-27 13:50:15 +02:00
Raphael Michel
ef0024b2ef Payment deadline delay: Respect week days 2023-07-27 13:49:31 +02:00
Raphael Michel
8e603410fa Update po files
[CI skip]

Signed-off-by: Raphael Michel <michel@rami.io>
2023-07-27 10:38:23 +02:00
Raphael Michel
16691ca2f6 Prevent 65ecdc184 clashing with forms that have a field called template 2023-07-26 19:18:53 +02:00
Raphael Michel
d7e70fd0b9 Order change: Do not expose internal name 2023-07-26 15:41:15 +02:00
Raphael Michel
071a3e2c9b PDF layouts: Allow negative numbers in JSON schema 2023-07-26 15:41:15 +02:00
Raphael Michel
1733c383b3 Docs: Add description of NFC support (#3494)
* Add documentation on NFC support

* Add a .

* Update doc/development/nfc/uid.rst

Co-authored-by: robbi5 <richt@rami.io>

---------

Co-authored-by: robbi5 <richt@rami.io>
2023-07-26 13:26:00 +02:00
55 changed files with 12387 additions and 11536 deletions

View File

@@ -12,3 +12,4 @@ Developer documentation
api/index
structure
translation/index
nfc/index

View File

@@ -0,0 +1,15 @@
NFC media
=========
pretix supports using NFC chips as "reusable media", for example to store gift cards or tickets.
Most of this implementation currently lives in our proprietary app pretixPOS, but in the future might also become part of our open-source pretixSCAN solution.
Either way, we want this to be an open ecosystem and therefore document the exact mechanisms in use on the following pages.
We support multiple implementations of NFC media, each documented on its own page:
.. toctree::
:maxdepth: 2
uid
mf0aes

View File

@@ -0,0 +1,113 @@
Mifare Ultralight AES
=====================
We offer an implementation that provides a higher security level than the UID-based approach and uses the `Mifare Ultralight AES`_ chip sold by NXP.
We believe the security model of this approach is adequate to the situation where this will usually be used and we'll outline known risks below.
If you want to dive deeper into the properties of the Mifare Ultralight AES chip, we recommend reading the `data sheet`_.
Random UIDs
-----------
Mifare Ultralight AES supports a feature that returns a randomized UID every time a non-authenticated user tries to
read the UID. This has a strong privacy benefit, since no unauthorized entity can use the NFC chips to track users.
On the other hand, this reduces interoperability of the system. For example, this prevents you from using the same NFC
chips for a different purpose where you only need the UID. This will also prevent your guests from reading their UID
themselves with their phones, which might be useful e.g. in debugging situations.
Since there's no one-size-fits-all choice here, you can enable or disable this feature in the pretix organizer
settings. If you change it, the change will apply to all newly encoded chips after the change.
Key management
--------------
For every organizer, the server will generate create a "key set", which consists of a publicly known ID (random 32-bit integer) and two 16-byte keys ("diversification key" and "UID key").
Using our :ref:`Device authentication mechanism <rest-deviceauth>`, an authorized device can submit a locally generated RSA public key to the server.
This key can no longer changed on the server once it is set, thus protecting against the attack scenario of a leaked device API token.
The server will then include key sets in the response to ``/api/v1/device/info``, encrypted with the device's RSA key.
This includes all key sets generated for the organizer the device belongs to, as well as all keys of organizers that have granted sufficient access to this organizer.
The device will decrypt the key sets using its RSA key and store the key sets locally.
.. warning:: The device **will** have access to the raw key sets. Therefore, there is a risk of leaked master keys if an
authorized device is stolen or abused. Our implementation in pretixPOS attempts to make this very hard on
modern, non-rooted Android devices by keeping them encrypted with the RSA key and only storing the RSA key
in the hardware-backed keystore of the device. A sufficiently motivated attacker, however, will likely still
be able to extract the keys from a stolen device.
Encoding a chip
---------------
When a new chip is encoded, the following steps will be taken:
- The UID of the chip is retrieved.
- A chip-specific key is generated using the mechanism documented in `AN10922`_ using the "diversification key" from the
organizer's key set as the CMAC key and the diversification input concatenated in the from of ``0x01 + UID + APPID + SYSTEMID``
with the following values:
- The UID of the chip as ``UID``
- ``"eu.pretix"`` (``0x65 0x75 0x2e 0x70 0x72 0x65 0x74 0x69 0x78``) as ``APPID``
- The ``public_id`` from the organizer's key set as a 4-byte big-endian value as ``SYSTEMID``
- The chip-specific key is written to the chip as the "data protection key" (config pages 0x30 to 0x33)
- The UID key from the organizer's key set is written to the chip as the "UID retrieval key" (config pages 0x34 to 0x37)
- The config page 0x29 is set like this:
- ``RID_ACT`` (random UID) to ``1`` or ``0`` based on the organizer's configuration
- ``SEC_MSG_ACT`` (secure messaging) to ``1``
- ``AUTH0`` (first page that needs authentication) to 0x04 (first non-UID page)
- The config page 0x2A is set like this:
- ``PROT`` to ``0`` (only write access restricted, not read access)
- ``AUTHLIM`` to ``256`` (maximum number of wrong authentications before "self-desctruction")
- Everything else to its default value (no lock bits are set)
- The ``public_id`` of the key set will be written to page 0x04 as a big-endian value
- The UID of the chip will be registered as a reusable medium on the server.
.. warning:: During encoding, the chip-specific key and the UID key are transmitted in plain text over the air. The
security model therefore relies on the encoding of chips being performed in a trusted physical environment
to prevent a nearby attacker from sniffing the keys with a strong antenna.
.. note:: If an attacker tries to authenticate with the chip 256 times using the wrong key, the chip will become
unusable. A chip may also become unusable if it is detached from the reader in the middle of the encoding
process (even though we've tried to implement it in a way that makes this unlikely).
Usage
-----
When a chip is presented to the NFC reader, the following steps will be taken:
- Command ``GET_VERSION`` is used to determine if it is a Mifare Ultralight AES chip (if not, abort).
- Page 0x04 is read. If it is all zeroes, the chip is considered un-encoded (abort). If it contains a value that
corresponds to the ``public_id`` of a known key set, this key set is used for all further operations. If it contains
a different value, we consider this chip to belong to a different organizer or not to a pretix system at all (abort).
- An authentication with the chip using the UID key is performed.
- The UID of the chip will be read.
- The chip-specific key will be derived using the mechanism described above in the encoding step.
- An authentication with the chip using the chip-specific key is performed. If this is fully successful, this step
proves that the chip knows the same chip-specific key as we do and is therefore an authentic chip encoded by us and
we can trust its UID value.
- The UID is transmitted to the server to fetch the correct medium.
During these steps, the keys are never transmitted in plain text and can thus not be sniffed by a nearby attacker
with a strong antenna.
.. _Mifare Ultralight AES: https://www.nxp.com/products/rfid-nfc/mifare-hf/mifare-ultralight/mifare-ultralight-aes-enhanced-security-for-limited-use-contactless-applications:MF0AESx20
.. _data sheet: https://www.nxp.com/docs/en/data-sheet/MF0AES(H)20.pdf
.. _AN10922: https://www.nxp.com/docs/en/application-note/AN10922.pdf

View File

@@ -0,0 +1,10 @@
UID-based
=========
With UID-based NFC, only the unique ID (UID) of the NFC chip is used for identification purposes.
This can be used with virtually all NFC chips that provide compatibility with the NFC reader in use, typically at least all chips that comply with ISO/IEC 14443-3A.
We make only one restriction: The UID may not start with ``08``, since that usually signifies a randomized UID that changes on every read (which would not be very useful).
.. warning:: The UID-based approach provides only a very low level of security. It is easy to clone a chip with the same
UID and impersonate someone else.

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2023.7.0.dev0"
__version__ = "2023.7.0"

View File

@@ -907,6 +907,11 @@ class Order(LockModel, LoggedModel):
return self.expires
expires = self.expires.date() + timedelta(days=delay)
if self.event.settings.get('payment_term_weekdays'):
if expires.weekday() == 5:
expires += timedelta(days=2)
elif expires.weekday() == 6:
expires += timedelta(days=1)
tz = ZoneInfo(self.event.settings.timezone)
expires = make_aware(datetime.combine(

View File

@@ -941,9 +941,9 @@ DEFAULTS = {
'form_kwargs': dict(
label=_('Expiration delay'),
help_text=_("The order will only actually expire this many days after the expiration date communicated "
"to the customer. However, this will not delay beyond the \"last date of payments\" "
"configured above, which is always enforced. The delay may also end on a weekend regardless "
"of the other settings above."),
"to the customer. If you select \"Only end payment terms on weekdays\" above, this will also "
"be respected. However, this will not delay beyond the \"last date of payments\" "
"configured above, which is always enforced."),
# Every order in between the official expiry date and the delayed expiry date has a performance penalty
# for the cron job, so we limit this feature to 30 days to prevent arbitrary numbers of orders needing
# to be checked.

View File

@@ -187,7 +187,7 @@
{% endif %}
{% for f in plugin_forms %}
{% if f.is_layouts and not f.title %}
{% if f.template %}
{% if f.template and not "template" in f.fields %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
@@ -261,7 +261,7 @@
{% bootstrap_field form.show_quota_left layout="control" %}
{% for f in plugin_forms %}
{% if not f.is_layouts and not f.title %}
{% if f.template %}
{% if f.template and not "template" in f.fields %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}
@@ -273,7 +273,7 @@
{% if not f.is_layouts and f.title %}
<fieldset>
<legend>{{ f.title }}</legend>
{% if f.template %}
{% if f.template and not "template" in f.fields %}
{% include f.template with form=f %}
{% else %}
{% bootstrap_form f layout="control" %}

View File

@@ -783,8 +783,8 @@ class MailSettingsRendererPreview(MailSettingsPreview):
return ctx
def get(self, request, *args, **kwargs):
v = str(request.event.settings.mail_text_order_placed)
v = format_map(v, self.placeholders('mail_text_order_placed'))
v = str(request.event.settings.mail_text_order_payment_failed)
v = format_map(v, self.placeholders('mail_text_order_payment_failed'))
renderers = request.event.get_html_mail_renderers()
if request.GET.get('renderer') in renderers:
with rolledback_transaction():

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-07-21 13:02+0000\n"
"POT-Creation-Date: 2023-07-27 11:50+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
<strong>{{ position.item }}</strong>
<strong>{{ position.item.name }}</strong>
{% if position.variation %}
{{ position.variation }}
{{ position.variation.value }}
{% endif %}
</h3>
</div>

View File

@@ -31,7 +31,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -43,7 +43,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -73,7 +73,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -110,7 +110,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -122,7 +122,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -138,7 +138,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -150,7 +150,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
}
@@ -188,7 +188,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -200,7 +200,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -212,7 +212,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -247,7 +247,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -259,7 +259,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -307,7 +307,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
}
@@ -339,7 +339,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -351,7 +351,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
},
@@ -371,7 +371,7 @@
},
{
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$"
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
]
}

View File

@@ -412,6 +412,7 @@ def test_expiring_auto_disabled(event):
def test_expiring_auto_delayed(event):
event.settings.set('payment_term_expire_delay_days', 3)
event.settings.set('payment_term_last', date(2023, 7, 2))
event.settings.set('payment_term_weekdays', False)
event.settings.set('timezone', 'Europe/Berlin')
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
@@ -458,6 +459,21 @@ def test_expiring_auto_delayed(event):
assert o2.status == Order.STATUS_EXPIRED
@pytest.mark.django_db
def test_expiring_auto_delayed_weekdays(event):
event.settings.set('payment_term_expire_delay_days', 2)
event.settings.set('payment_term_weekdays', True)
event.settings.set('timezone', 'Europe/Berlin')
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
expires=datetime(2023, 6, 30, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
total=0,
)
assert o1.payment_term_expire_date == o1.expires + timedelta(days=3)
@pytest.mark.django_db
def test_do_not_expire_if_approval_pending(event):
o1 = Order.objects.create(