Peppol: Live ID validation (#5602)

* Peppol: Live ID validation

* Always check both systems

* Simplify logic
This commit is contained in:
Raphael Michel
2025-11-27 19:50:53 +01:00
committed by GitHub
parent 0f82e1cae6
commit 2261951b15
2 changed files with 32 additions and 1 deletions

View File

@@ -35,6 +35,7 @@ dependencies = [
"cryptography>=44.0.0", "cryptography>=44.0.0",
"css-inline==0.18.*", "css-inline==0.18.*",
"defusedcsv>=1.1.0", "defusedcsv>=1.1.0",
"dnspython==2.*",
"Django[argon2]==4.2.*,>=4.2.26", "Django[argon2]==4.2.*,>=4.2.26",
"django-bootstrap3==25.2", "django-bootstrap3==25.2",
"django-compressor==4.5.1", "django-compressor==4.5.1",

View File

@@ -19,8 +19,11 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
import base64
import hashlib
import re import re
import dns.resolver
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _, pgettext from django.utils.translation import gettext_lazy as _, pgettext
@@ -123,6 +126,9 @@ class PeppolIdValidator:
"9959": ".*", "9959": ".*",
} }
def __init__(self, validate_online=False):
self.validate_online = validate_online
def __call__(self, value): def __call__(self, value):
if ":" not in value: if ":" not in value:
raise ValidationError(_("A Peppol participant ID always starts with a prefix, followed by a colon (:).")) raise ValidationError(_("A Peppol participant ID always starts with a prefix, followed by a colon (:)."))
@@ -136,6 +142,28 @@ class PeppolIdValidator:
raise ValidationError(_("The Peppol participant ID does not match the validation rules for the prefix " raise ValidationError(_("The Peppol participant ID does not match the validation rules for the prefix "
"%(number)s. Please reach out to us if you are sure this ID is correct."), "%(number)s. Please reach out to us if you are sure this ID is correct."),
params={"number": prefix}) params={"number": prefix})
if self.validate_online:
base_hostnames = ['edelivery.tech.ec.europa.eu', 'acc.edelivery.tech.ec.europa.eu']
smp_id = base64.b32encode(hashlib.sha256(value.lower().encode()).digest()).decode().rstrip("=")
for base_hostname in base_hostnames:
smp_domain = f'{smp_id}.iso6523-actorid-upis.{base_hostname}'
resolver = dns.resolver.Resolver()
try:
answers = resolver.resolve(smp_domain, 'NAPTR', lifetime=1.0)
if answers:
return value
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
# ID not registered, do not set found=True
pass
except Exception: # noqa
# Error likely on our end or infrastructure is down, allow user to proceed
return value
raise ValidationError(
_("The Peppol participant ID is not registered on the Peppol network."),
)
return value return value
@@ -155,7 +183,9 @@ class PeppolTransmissionType(TransmissionType):
"transmission_peppol_participant_id": forms.CharField( "transmission_peppol_participant_id": forms.CharField(
label=_("Peppol participant ID"), label=_("Peppol participant ID"),
validators=[ validators=[
PeppolIdValidator(), PeppolIdValidator(
validate_online=True,
),
] ]
), ),
} }