core: add validator which allows for URLs with formatting (#4890)

This commit is contained in:
Jens L 2023-03-10 00:16:17 +01:00 committed by GitHub
parent 9b8c0e3924
commit 86bb2afd02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 3 deletions

View File

@ -182,7 +182,9 @@ class Migration(migrations.Migration):
model_name="application",
name="meta_launch_url",
field=models.TextField(
blank=True, default="", validators=[authentik.lib.models.DomainlessURLValidator()]
blank=True,
default="",
validators=[authentik.lib.models.DomainlessFormattedURLValidator()],
),
),
migrations.RunPython(

View File

@ -26,7 +26,11 @@ from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.generators import generate_id
from authentik.lib.models import CreatedUpdatedModel, DomainlessURLValidator, SerializerModel
from authentik.lib.models import (
CreatedUpdatedModel,
DomainlessFormattedURLValidator,
SerializerModel,
)
from authentik.lib.utils.http import get_client_ip
from authentik.policies.models import PolicyBindingModel
@ -291,7 +295,7 @@ class Application(SerializerModel, PolicyBindingModel):
)
meta_launch_url = models.TextField(
default="", blank=True, validators=[DomainlessURLValidator()]
default="", blank=True, validators=[DomainlessFormattedURLValidator()]
)
open_in_new_tab = models.BooleanField(

View File

@ -37,6 +37,22 @@ class TestApplicationsAPI(APITestCase):
order=0,
)
def test_formatted_launch_url(self):
"""Test formatted launch URL"""
self.client.force_login(self.user)
self.assertEqual(
self.client.patch(
reverse("authentik_api:application-detail", kwargs={"slug": self.allowed.slug}),
{"meta_launch_url": "https://%(username)s.test.goauthentik.io/%(username)s"},
).status_code,
200,
)
self.allowed.refresh_from_db()
self.assertEqual(
self.allowed.get_launch_url(self.user),
f"https://{self.user.username}.test.goauthentik.io/{self.user.username}",
)
def test_set_icon(self):
"""Test set_icon"""
file = ContentFile(b"text", "name")

View File

@ -74,3 +74,21 @@ class DomainlessURLValidator(URLValidator):
if scheme not in self.schemes:
value = "default" + value
super().__call__(value)
class DomainlessFormattedURLValidator(DomainlessURLValidator):
"""URL validator which allows for python format strings"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.host_re = r"([%\(\)a-zA-Z])+" + self.domain_re + self.domain_re
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,
)
self.schemes = ["http", "https", "blank"] + list(self.schemes)