e36d7928e4
add New fields for - assertion_valid_not_before - assertion_valid_not_on_or_after - session_valid_not_on_or_after allow flexible time durations for these fields fall back to Provider's ACS if none is specified in AuthNRequest
121 lines
3.8 KiB
Python
121 lines
3.8 KiB
Python
"""passbook saml_idp Models"""
|
|
from django.contrib.postgres.fields import ArrayField
|
|
from django.db import models
|
|
from django.shortcuts import reverse
|
|
from django.utils.translation import gettext as _
|
|
from structlog import get_logger
|
|
|
|
from passbook.core.models import PropertyMapping, Provider
|
|
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
|
from passbook.providers.saml.processors.base import Processor
|
|
from passbook.providers.saml.utils.time import timedelta_string_validator
|
|
|
|
LOGGER = get_logger()
|
|
|
|
|
|
class SAMLProvider(Provider):
|
|
"""Model to save information about a Remote SAML Endpoint"""
|
|
|
|
name = models.TextField()
|
|
processor_path = models.CharField(max_length=255, choices=[])
|
|
|
|
acs_url = models.URLField()
|
|
audience = models.TextField(default="")
|
|
issuer = models.TextField()
|
|
|
|
assertion_valid_not_before = models.TextField(
|
|
default="minutes=5",
|
|
validators=[timedelta_string_validator],
|
|
help_text=_(
|
|
(
|
|
"Assertion valid not before current time - this value "
|
|
"(Format: hours=1;minutes=2;seconds=3)."
|
|
)
|
|
),
|
|
)
|
|
assertion_valid_not_on_or_after = models.TextField(
|
|
default="minutes=5",
|
|
validators=[timedelta_string_validator],
|
|
help_text=_(
|
|
(
|
|
"Assertion not valid on or after current time + this value "
|
|
"(Format: hours=1;minutes=2;seconds=3)."
|
|
)
|
|
),
|
|
)
|
|
|
|
session_valid_not_on_or_after = models.TextField(
|
|
default="minutes=86400",
|
|
validators=[timedelta_string_validator],
|
|
help_text=_(
|
|
(
|
|
"Session not valid on or after current time + this value "
|
|
"(Format: hours=1;minutes=2;seconds=3)."
|
|
)
|
|
),
|
|
)
|
|
|
|
signing = models.BooleanField(default=True)
|
|
signing_cert = models.TextField()
|
|
signing_key = models.TextField()
|
|
|
|
form = "passbook.providers.saml.forms.SAMLProviderForm"
|
|
_processor = None
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self._meta.get_field("processor_path").choices = get_provider_choices()
|
|
|
|
@property
|
|
def processor(self):
|
|
"""Return selected processor as instance"""
|
|
if not self._processor:
|
|
try:
|
|
self._processor = path_to_class(self.processor_path)(self)
|
|
except ImportError as exc:
|
|
LOGGER.warning(exc)
|
|
self._processor = None
|
|
return self._processor
|
|
|
|
def __str__(self):
|
|
return f"SAML Provider {self.name}"
|
|
|
|
def link_download_metadata(self):
|
|
"""Get link to download XML metadata for admin interface"""
|
|
try:
|
|
# pylint: disable=no-member
|
|
return reverse(
|
|
"passbook_providers_saml:saml-metadata",
|
|
kwargs={"application": self.application.slug},
|
|
)
|
|
except Provider.application.RelatedObjectDoesNotExist:
|
|
return None
|
|
|
|
class Meta:
|
|
|
|
verbose_name = _("SAML Provider")
|
|
verbose_name_plural = _("SAML Providers")
|
|
|
|
|
|
class SAMLPropertyMapping(PropertyMapping):
|
|
"""SAML Property mapping, allowing Name/FriendlyName mapping to a list of strings"""
|
|
|
|
saml_name = models.TextField()
|
|
friendly_name = models.TextField(default=None, blank=True, null=True)
|
|
values = ArrayField(models.TextField())
|
|
|
|
form = "passbook.providers.saml.forms.SAMLPropertyMappingForm"
|
|
|
|
def __str__(self):
|
|
return f"SAML Property Mapping {self.saml_name}"
|
|
|
|
class Meta:
|
|
|
|
verbose_name = _("SAML Property Mapping")
|
|
verbose_name_plural = _("SAML Property Mappings")
|
|
|
|
|
|
def get_provider_choices():
|
|
"""Return tuple of class_path, class name of all providers."""
|
|
return [(class_to_path(x), x.__name__) for x in Processor.__subclasses__()]
|