Compare commits

..
11 changed files with 83 additions and 98 deletions
+9 -15
View File
@@ -53,7 +53,6 @@ from django.db.models import QuerySet
from django.forms import Select, widgets
from django.forms.widgets import FILE_INPUT_CONTRADICTION
from django.utils.formats import date_format
from django.utils.functional import lazy
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import format_lazy
@@ -325,21 +324,16 @@ class WrappedPhonePrefixSelect(Select):
initial = None
def __init__(self, initial=None):
def _get_choices():
choices = [("", "---------")]
if initial:
for prefix, values in COUNTRY_CODE_TO_REGION_CODE.items():
if all(v == REGION_CODE_FOR_NON_GEO_ENTITY for v in values):
continue
if initial in values:
self.initial = "+%d" % prefix
break
choices += get_phone_prefixes_sorted_and_localized()
return choices
choices = lazy(_get_choices, list)()
choices = [("", "---------")]
if initial:
for prefix, values in COUNTRY_CODE_TO_REGION_CODE.items():
if all(v == REGION_CODE_FOR_NON_GEO_ENTITY for v in values):
continue
if initial in values:
self.initial = "+%d" % prefix
break
choices += get_phone_prefixes_sorted_and_localized()
super().__init__(choices=choices, attrs={
'aria-label': pgettext_lazy('phonenumber', 'International area code'),
'autocomplete': 'tel-country-code',
@@ -1,29 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# 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/>.
#
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Do nothing. Useful for startup performance testing."
def handle(self, *args, **options):
pass
@@ -0,0 +1,18 @@
# Generated by Django 5.2.12 on 2026-07-01 08:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0301_reusablemedium_remove_orderposition"),
]
operations = [
migrations.AddField(
model_name="customerssoprovider",
name="allow_convert_to_sso",
field=models.BooleanField(default=False),
),
]
+7
View File
@@ -73,6 +73,13 @@ class CustomerSSOProvider(LoggedModel):
null=False, blank=False,
choices=METHODS,
)
allow_convert_to_sso = models.BooleanField(
default=False,
verbose_name=_("Convert existing customers to single-sign-on accounts on login"),
help_text=_(
"If enabled, when an existing customer registered with email and password tries to login through this SSO provider, pretix changes the account to single-sign-on. Otherwise pretix does not allow to log in."
),
)
configuration = models.JSONField()
def allow_delete(self):
+1 -1
View File
@@ -899,7 +899,7 @@ class Event(EventMixin, LoggedModel):
self.save()
self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk})
if hasattr(other, 'alternative_domain_assignment'):
if hasattr(other, 'alternative_domain_assignment') and not is_cross_organizer:
other.alternative_domain_assignment.domain.event_assignments.create(event=self)
if not self.all_sales_channels:
+1 -1
View File
@@ -1673,7 +1673,7 @@ class CountriesAndEUAndStates(CountriesAndEU):
class TaxRuleLineForm(I18nForm):
country = LazyTypedChoiceField(
choices=lazy(lambda: CountriesAndEUAndStates(), CountriesAndEUAndStates),
choices=CountriesAndEUAndStates(),
required=False
)
address_type = forms.ChoiceField(
+1 -1
View File
@@ -1244,7 +1244,7 @@ class SSOProviderForm(I18nModelForm):
class Meta:
model = CustomerSSOProvider
fields = ['is_active', 'name', 'button_label', 'method']
fields = ['is_active', 'name', 'button_label', 'method', 'allow_convert_to_sso']
widgets = {
'method': forms.RadioSelect,
}
@@ -132,6 +132,7 @@
<legend>{% trans "Customer accounts" %}</legend>
{% bootstrap_field sform.customer_accounts layout="control" %}
{% bootstrap_field sform.customer_accounts_native layout="control" %}
{% bootstrap_field sform.customer_accounts_to_oidc layout="control" %}
{% bootstrap_field sform.customer_accounts_require_login_for_order_access layout="control" %}
{% bootstrap_field sform.customer_accounts_link_by_email layout="control" %}
{% bootstrap_field sform.name_scheme layout="control" %}
+27 -5
View File
@@ -864,11 +864,33 @@ class SSOLoginReturnView(RedirectBackMixin, View):
identifier=identifier,
)
except Customer.DoesNotExist:
return self._fail(
_('We were unable to use your login since the email address {email} is already used for a '
'different account in this system.').format(email=profile['email']),
popup_origin,
)
# no race-condition, try to convert to oidc?
if self.provider.allow_convert_to_sso:
try:
customer = self.request.organizer.customers.get(
email=profile['email'],
)
customer.set_unusable_password()
customer.provider = self.provider
customer.external_identifier = str(profile['uid'])
customer.is_verified = True
if name_parts:
customer.name_parts = name_parts
if profile.get('phone'):
customer.phone = profile.get('phone')
customer.save()
except Customer.DoesNotExist:
# should actually never happen
return self._fail(
_('We were unable to use your login since either the email address is unknown in this system.'),
popup_origin,
)
else:
return self._fail(
_('We were unable to use your login since the email address {email} is already used for a '
'different account in this system.').format(email=profile['email']),
popup_origin,
)
else:
if customer.is_active and customer.email != profile['email']:
customer.email = profile['email']
+18
View File
@@ -438,6 +438,24 @@ def test_org_sso_login_new_customer_email_conflict(env, client, provider):
_sso_login(client, provider, 'new@example.net', expect_fail=True)
@pytest.mark.django_db(transaction=True)
def test_org_sso_convert_customer_on_login(env, client, provider):
organizer = env[0]
provider.allow_convert_to_sso = True
provider.save()
with scopes_disabled():
customer = organizer.customers.create(email='new@example.net', is_verified=True, is_active=False)
customer.set_password('foo')
customer.save()
_sso_login(client, provider, 'new@example.net')
customer.refresh_from_db()
assert customer.provider
assert customer.identifier
assert not customer.has_usable_password()
@pytest.mark.django_db
@pytest.mark.parametrize("url", [
"account/change",
-46
View File
@@ -1,46 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# 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/>.
#
import os
import subprocess
import sys
import tempfile
def test_start_with_redis_down():
"""
This is a test that ensures that pretix is able to start without a running redis server,
even if one is configured.
"""
with tempfile.NamedTemporaryFile(suffix="cfg") as f:
f.write(b"[redis]\nlocation=redis://127.0.0.99:65534/2\n")
f.flush()
assert subprocess.check_call(
[
sys.executable,
os.path.join(os.path.dirname(__file__), '../manage.py'),
"noop",
],
env={
"PRETIX_CONFIG_FILE": f.name,
}
) == 0