2018-11-26 16:18:56 +00:00
|
|
|
"""passbook LDAP Models"""
|
2020-09-19 13:25:17 +00:00
|
|
|
from datetime import datetime
|
2020-07-20 13:11:27 +00:00
|
|
|
from typing import Optional, Type
|
2018-11-11 12:41:48 +00:00
|
|
|
|
2020-09-19 13:25:17 +00:00
|
|
|
from django.core.cache import cache
|
2018-11-26 16:18:56 +00:00
|
|
|
from django.db import models
|
2020-07-20 13:11:27 +00:00
|
|
|
from django.forms import ModelForm
|
2020-02-16 11:30:45 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2020-05-23 20:01:38 +00:00
|
|
|
from ldap3 import Connection, Server
|
2018-11-11 12:41:48 +00:00
|
|
|
|
2019-10-10 15:36:09 +00:00
|
|
|
from passbook.core.models import Group, PropertyMapping, Source
|
2020-09-13 19:52:34 +00:00
|
|
|
from passbook.lib.models import DomainlessURLValidator
|
2020-09-19 13:25:17 +00:00
|
|
|
from passbook.lib.utils.template import render_to_string
|
2018-11-26 16:18:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LDAPSource(Source):
|
2020-07-01 16:40:52 +00:00
|
|
|
"""Federate LDAP Directory with passbook, or create new accounts in LDAP."""
|
2018-11-26 16:18:56 +00:00
|
|
|
|
2020-02-16 11:30:45 +00:00
|
|
|
server_uri = models.TextField(
|
2020-09-13 19:52:34 +00:00
|
|
|
validators=[DomainlessURLValidator(schemes=["ldap", "ldaps"])],
|
2020-02-16 11:30:45 +00:00
|
|
|
verbose_name=_("Server URI"),
|
|
|
|
)
|
|
|
|
bind_cn = models.TextField(verbose_name=_("Bind CN"))
|
2018-11-26 16:18:56 +00:00
|
|
|
bind_password = models.TextField()
|
2020-02-16 11:30:45 +00:00
|
|
|
start_tls = models.BooleanField(default=False, verbose_name=_("Enable Start TLS"))
|
2018-11-26 16:18:56 +00:00
|
|
|
|
2020-02-16 11:30:45 +00:00
|
|
|
base_dn = models.TextField(verbose_name=_("Base DN"))
|
2019-12-31 11:51:16 +00:00
|
|
|
additional_user_dn = models.TextField(
|
2020-02-16 11:30:45 +00:00
|
|
|
help_text=_("Prepended to Base DN for User-queries."),
|
|
|
|
verbose_name=_("Addition User DN"),
|
2020-05-23 20:01:38 +00:00
|
|
|
blank=True,
|
2019-12-31 11:51:16 +00:00
|
|
|
)
|
|
|
|
additional_group_dn = models.TextField(
|
2020-02-16 11:30:45 +00:00
|
|
|
help_text=_("Prepended to Base DN for Group-queries."),
|
|
|
|
verbose_name=_("Addition Group DN"),
|
2020-05-23 20:01:38 +00:00
|
|
|
blank=True,
|
2019-12-31 11:51:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
user_object_filter = models.TextField(
|
|
|
|
default="(objectCategory=Person)",
|
|
|
|
help_text=_("Consider Objects matching this filter to be Users."),
|
|
|
|
)
|
|
|
|
user_group_membership_field = models.TextField(
|
|
|
|
default="memberOf", help_text=_("Field which contains Groups of user.")
|
|
|
|
)
|
|
|
|
group_object_filter = models.TextField(
|
|
|
|
default="(objectCategory=Group)",
|
|
|
|
help_text=_("Consider Objects matching this filter to be Groups."),
|
|
|
|
)
|
|
|
|
object_uniqueness_field = models.TextField(
|
|
|
|
default="objectSid", help_text=_("Field which contains a unique Identifier.")
|
|
|
|
)
|
2019-10-10 15:36:09 +00:00
|
|
|
|
2020-05-23 20:01:38 +00:00
|
|
|
sync_users = models.BooleanField(default=True)
|
2019-10-10 15:36:09 +00:00
|
|
|
sync_groups = models.BooleanField(default=True)
|
2019-12-31 11:51:16 +00:00
|
|
|
sync_parent_group = models.ForeignKey(
|
|
|
|
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
|
|
|
|
)
|
2018-11-26 16:18:56 +00:00
|
|
|
|
2020-07-20 13:11:27 +00:00
|
|
|
def form(self) -> Type[ModelForm]:
|
|
|
|
from passbook.sources.ldap.forms import LDAPSourceForm
|
|
|
|
|
|
|
|
return LDAPSourceForm
|
2018-11-26 17:22:38 +00:00
|
|
|
|
2020-09-19 13:25:17 +00:00
|
|
|
def state_cache_prefix(self, suffix: str) -> str:
|
|
|
|
"""Key by which the ldap source status is saved"""
|
|
|
|
return f"source_ldap_{self.pk}_state_{suffix}"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def ui_additional_info(self) -> str:
|
|
|
|
last_sync = cache.get(self.state_cache_prefix("last_sync"), None)
|
|
|
|
if last_sync:
|
|
|
|
last_sync = datetime.fromtimestamp(last_sync)
|
|
|
|
|
|
|
|
return render_to_string(
|
|
|
|
"ldap/source_list_status.html", {"source": self, "last_sync": last_sync}
|
|
|
|
)
|
|
|
|
|
2020-06-08 23:17:17 +00:00
|
|
|
_connection: Optional[Connection] = None
|
2020-05-23 20:01:38 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def connection(self) -> Connection:
|
|
|
|
"""Get a fully connected and bound LDAP Connection"""
|
|
|
|
if not self._connection:
|
|
|
|
server = Server(self.server_uri)
|
|
|
|
self._connection = Connection(
|
|
|
|
server,
|
|
|
|
raise_exceptions=True,
|
|
|
|
user=self.bind_cn,
|
|
|
|
password=self.bind_password,
|
|
|
|
)
|
|
|
|
|
|
|
|
self._connection.bind()
|
|
|
|
if self.start_tls:
|
|
|
|
self._connection.start_tls()
|
|
|
|
return self._connection
|
|
|
|
|
2018-11-26 16:18:56 +00:00
|
|
|
class Meta:
|
|
|
|
|
2019-12-31 11:51:16 +00:00
|
|
|
verbose_name = _("LDAP Source")
|
|
|
|
verbose_name_plural = _("LDAP Sources")
|
2018-11-11 12:41:48 +00:00
|
|
|
|
|
|
|
|
2019-10-10 15:36:09 +00:00
|
|
|
class LDAPPropertyMapping(PropertyMapping):
|
2020-07-01 16:40:52 +00:00
|
|
|
"""Map LDAP Property to User or Group object attribute"""
|
2018-11-11 12:41:48 +00:00
|
|
|
|
2019-10-10 15:36:09 +00:00
|
|
|
object_field = models.TextField()
|
2019-10-11 10:53:48 +00:00
|
|
|
|
2020-07-20 14:43:30 +00:00
|
|
|
def form(self) -> Type[ModelForm]:
|
|
|
|
from passbook.sources.ldap.forms import LDAPPropertyMappingForm
|
|
|
|
|
|
|
|
return LDAPPropertyMappingForm
|
2019-10-11 10:53:48 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
2020-02-17 19:38:14 +00:00
|
|
|
return f"LDAP Property Mapping {self.expression} -> {self.object_field}"
|
2019-10-11 10:53:48 +00:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
2019-12-31 11:51:16 +00:00
|
|
|
verbose_name = _("LDAP Property Mapping")
|
|
|
|
verbose_name_plural = _("LDAP Property Mappings")
|