From 7fe9b8f0b43ab4f10751777509204109ac827fab Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 13 Sep 2020 21:52:34 +0200 Subject: [PATCH] providers/proxy: add domainless URL Validator --- passbook/lib/models.py | 22 +++++++++++ .../migrations/0004_auto_20200913_1947.py | 37 +++++++++++++++++++ passbook/providers/proxy/models.py | 6 +-- .../migrations/0005_auto_20200913_1947.py | 27 ++++++++++++++ passbook/sources/ldap/models.py | 4 +- 5 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 passbook/providers/proxy/migrations/0004_auto_20200913_1947.py create mode 100644 passbook/sources/ldap/migrations/0005_auto_20200913_1947.py diff --git a/passbook/lib/models.py b/passbook/lib/models.py index 80fbcd3c3..ca5589e55 100644 --- a/passbook/lib/models.py +++ b/passbook/lib/models.py @@ -1,5 +1,9 @@ """Generic models""" +import re + +from django.core.validators import URLValidator from django.db import models +from django.utils.regex_helper import _lazy_re_compile from model_utils.managers import InheritanceManager from rest_framework.serializers import BaseSerializer @@ -48,3 +52,21 @@ class InheritanceForeignKey(models.ForeignKey): """Custom ForeignKey that uses InheritanceForwardManyToOneDescriptor""" forward_related_accessor_class = InheritanceForwardManyToOneDescriptor + + +class DomainlessURLValidator(URLValidator): + """Subclass of URLValidator which doesn't check the domain + (to allow hostnames without domain)""" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.host_re = "(" + self.hostname_re + self.domain_re + "|localhost)" + self.regex = _lazy_re_compile( + r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately + r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication + r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")" + r"(?::\d{2,5})?" # port + r"(?:[/?#][^\s]*)?" # resource path + r"\Z", + re.IGNORECASE, + ) diff --git a/passbook/providers/proxy/migrations/0004_auto_20200913_1947.py b/passbook/providers/proxy/migrations/0004_auto_20200913_1947.py new file mode 100644 index 000000000..518850aa1 --- /dev/null +++ b/passbook/providers/proxy/migrations/0004_auto_20200913_1947.py @@ -0,0 +1,37 @@ +# Generated by Django 3.1.1 on 2020-09-13 19:47 + +from django.db import migrations, models + +import passbook.lib.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("passbook_providers_proxy", "0003_proxyprovider_certificate"), + ] + + operations = [ + migrations.AlterField( + model_name="proxyprovider", + name="external_host", + field=models.TextField( + validators=[ + passbook.lib.models.DomainlessURLValidator( + schemes=("http", "https") + ) + ] + ), + ), + migrations.AlterField( + model_name="proxyprovider", + name="internal_host", + field=models.TextField( + validators=[ + passbook.lib.models.DomainlessURLValidator( + schemes=("http", "https") + ) + ] + ), + ), + ] diff --git a/passbook/providers/proxy/models.py b/passbook/providers/proxy/models.py index c853fd8ac..65c99e380 100644 --- a/passbook/providers/proxy/models.py +++ b/passbook/providers/proxy/models.py @@ -4,12 +4,12 @@ from random import SystemRandom from typing import Iterable, Type from urllib.parse import urljoin -from django.core.validators import URLValidator from django.db import models from django.forms import ModelForm from django.utils.translation import gettext as _ from passbook.crypto.models import CertificateKeyPair +from passbook.lib.models import DomainlessURLValidator from passbook.outposts.models import OutpostModel from passbook.providers.oauth2.constants import ( SCOPE_OPENID, @@ -41,10 +41,10 @@ class ProxyProvider(OutpostModel, OAuth2Provider): Protocols by using a Reverse-Proxy.""" internal_host = models.TextField( - validators=[URLValidator(schemes=("http", "https"))] + validators=[DomainlessURLValidator(schemes=("http", "https"))] ) external_host = models.TextField( - validators=[URLValidator(schemes=("http", "https"))] + validators=[DomainlessURLValidator(schemes=("http", "https"))] ) cookie_secret = models.TextField(default=get_cookie_secret) diff --git a/passbook/sources/ldap/migrations/0005_auto_20200913_1947.py b/passbook/sources/ldap/migrations/0005_auto_20200913_1947.py new file mode 100644 index 000000000..d081cdfa2 --- /dev/null +++ b/passbook/sources/ldap/migrations/0005_auto_20200913_1947.py @@ -0,0 +1,27 @@ +# Generated by Django 3.1.1 on 2020-09-13 19:47 + +from django.db import migrations, models + +import passbook.lib.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("passbook_sources_ldap", "0004_auto_20200524_1146"), + ] + + operations = [ + migrations.AlterField( + model_name="ldapsource", + name="server_uri", + field=models.TextField( + validators=[ + passbook.lib.models.DomainlessURLValidator( + schemes=["ldap", "ldaps"] + ) + ], + verbose_name="Server URI", + ), + ), + ] diff --git a/passbook/sources/ldap/models.py b/passbook/sources/ldap/models.py index 4fc1ffc3c..16cb14691 100644 --- a/passbook/sources/ldap/models.py +++ b/passbook/sources/ldap/models.py @@ -1,20 +1,20 @@ """passbook LDAP Models""" from typing import Optional, Type -from django.core.validators import URLValidator from django.db import models from django.forms import ModelForm from django.utils.translation import gettext_lazy as _ from ldap3 import Connection, Server from passbook.core.models import Group, PropertyMapping, Source +from passbook.lib.models import DomainlessURLValidator class LDAPSource(Source): """Federate LDAP Directory with passbook, or create new accounts in LDAP.""" server_uri = models.TextField( - validators=[URLValidator(schemes=["ldap", "ldaps"])], + validators=[DomainlessURLValidator(schemes=["ldap", "ldaps"])], verbose_name=_("Server URI"), ) bind_cn = models.TextField(verbose_name=_("Bind CN"))