Commit a77f7b1e authored by Pierre-Elliott Bécue's avatar Pierre-Elliott Bécue

New upstream version 1.4.0

parent 3f1cb097
ChangeLog
=========
1.4.0 (2019-07-11)
------------------
*New:*
* *[#177]* Add BooleanField.
* Add support for Django 2.2 and python 3.7
*Bugfix:*
* *[#3]* Allow to change objects found at subtree scope.
* *[#190]* Check bind state of connection before unbinding.
1.3.0 (2018-12-12)
------------------
......
Metadata-Version: 1.2
Name: django-ldapdb
Version: 1.3.0
Version: 1.4.0
Summary: An LDAP database backend for Django
Home-page: https://github.com/django-ldapdb/django-ldapdb
Author: Jeremy Laine
......@@ -41,7 +41,7 @@ Description: django-ldapdb
* Full admin support and browsing
``django-ldapdb`` supports Django versions 1.11 / 2.0 / 2.1, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
``django-ldapdb`` supports Django versions 1.11 / 2.1 / 2.2, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
as far as the Django and Python versions are compatible.
......@@ -152,6 +152,7 @@ Description: django-ldapdb
* ``IntegerField``
* ``FloatField``
* ``BooleanField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
......
......@@ -31,7 +31,7 @@ It supports most of the same APIs as a Django model:
* Full admin support and browsing
``django-ldapdb`` supports Django versions 1.11 / 2.0 / 2.1, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
``django-ldapdb`` supports Django versions 1.11 / 2.1 / 2.2, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
as far as the Django and Python versions are compatible.
......@@ -142,6 +142,7 @@ Similar to Django:
* ``IntegerField``
* ``FloatField``
* ``BooleanField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
......
Metadata-Version: 1.2
Name: django-ldapdb
Version: 1.3.0
Version: 1.4.0
Summary: An LDAP database backend for Django
Home-page: https://github.com/django-ldapdb/django-ldapdb
Author: Jeremy Laine
......@@ -41,7 +41,7 @@ Description: django-ldapdb
* Full admin support and browsing
``django-ldapdb`` supports Django versions 1.11 / 2.0 / 2.1, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
``django-ldapdb`` supports Django versions 1.11 / 2.1 / 2.2, and Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7,
as far as the Django and Python versions are compatible.
......@@ -152,6 +152,7 @@ Description: django-ldapdb
* ``IntegerField``
* ``FloatField``
* ``BooleanField``
* ``CharField``
* ``ImageField``
* ``DateTimeField``
......
......@@ -80,3 +80,23 @@ class LdapMultiPKRoom(ldapdb.models.Model):
def __str__(self):
return "%s (%s)" % (self.name, self.number)
class AbstractGroup(ldapdb.models.Model):
class Meta:
abstract = True
object_classes = ['posixGroup']
gid = fields.IntegerField(db_column='gidNumber', unique=True)
name = fields.CharField(db_column='cn', max_length=200, primary_key=True)
usernames = fields.ListField(db_column='memberUid')
def __str__(self):
return self.name
def __unicode__(self):
return self.name
class ConcreteGroup(AbstractGroup):
base_dn = "ou=groups,dc=example,dc=org"
......@@ -105,6 +105,7 @@ INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'ldapdb',
'examples',
'django.contrib.admin',
......
......@@ -9,6 +9,7 @@ import time
import factory
import factory.django
import factory.fuzzy
import ldap
import volatildap
from django.conf import settings
from django.contrib.auth import hashers as auth_hashers
......@@ -19,7 +20,7 @@ from django.db.models import Count, Q
from django.test import TestCase
from django.utils import timezone
from examples.models import LdapGroup, LdapMultiPKRoom, LdapUser
from examples.models import ConcreteGroup, LdapGroup, LdapMultiPKRoom, LdapUser
from ldapdb.backends.ldap.compiler import SQLCompiler, query_as_ldap
groups = ('ou=groups,dc=example,dc=org', {
......@@ -28,6 +29,8 @@ people = ('ou=people,dc=example,dc=org', {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']})
contacts = ('ou=contacts,ou=groups,dc=example,dc=org', {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']})
users = ('ou=users,ou=people,dc=example,dc=org', {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['users']})
rooms = ('ou=rooms,dc=example,dc=org', {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['rooms']})
foogroup = ('cn=foogroup,ou=groups,dc=example,dc=org', {
......@@ -65,6 +68,12 @@ foouser = ('uid=foouser,ou=people,dc=example,dc=org', {
'uidNumber': ['2000'], 'gidNumber': ['1000'], 'sn': [b'Us\xc3\xa9r'],
'homeDirectory': ['/home/foouser'], 'givenName': [b'F\xc3\xb4o'],
'uid': ['foouser']})
baruser = ('uid=baruser,ou=users,ou=people,dc=example,dc=org', {
'objectClass': ['posixAccount', 'shadowAccount', 'inetOrgPerson'],
'cn': ['Bar Test'], 'givenName': ['Test'], 'sn': ['Bar'],
'uid': ['baruser'], 'uidNumber': ['2001'], 'gidNumber': ['1000'],
'homeDirectory': ['/home/baruser'], 'loginShell': ['/bin/bash'],
'jpegPhoto': []})
class UserFactory(factory.django.DjangoModelFactory):
......@@ -87,6 +96,8 @@ class UserFactory(factory.django.DjangoModelFactory):
class BaseTestCase(TestCase):
directory = {}
databases = ['default', 'ldap']
@classmethod
def setUpClass(cls):
super(BaseTestCase, cls).setUpClass()
......@@ -533,8 +544,22 @@ class GroupTestCase(BaseTestCase):
self.assertEqual([], g.usernames)
class GroupSubclassingTestCase(BaseTestCase):
directory = dict([groups, foogroup, bargroup, wizgroup, people, foouser])
def test_concrete_group(self):
g = ConcreteGroup.objects.get(name='foogroup')
self.assertCountEqual(['foouser', 'baruser'], g.usernames)
g.name = 'modified'
g.save()
g = ConcreteGroup.objects.get(name='modified')
self.assertCountEqual(['foouser', 'baruser'], g.usernames)
class UserTestCase(BaseTestCase):
directory = dict([groups, people, foouser])
directory = dict([groups, people, users, foouser, baruser])
def test_verbose_name(self):
self.assertEqual("Prime name", LdapUser._meta.get_field('first_name').verbose_name)
......@@ -649,6 +674,18 @@ class UserTestCase(BaseTestCase):
u = LdapUser.objects.get(last_modified__in=[before, lm])
self.assertEqual(u.username, 'foouser')
def test_dn_consistency(self):
u = LdapUser.objects.get(username='baruser')
u.first_name = u"Barr"
try:
u.save()
except ldap.LDAPError:
self.fail("Cannot save object")
# DN shouldn't be changed
self.assertEqual(
LdapUser.objects.get(username='baruser').dn,
'uid=baruser,ou=users,ou=people,dc=example,dc=org')
class ScopedTestCase(BaseTestCase):
directory = dict([groups, people, foogroup, contacts])
......
......@@ -6,7 +6,6 @@
import sys
import ldap.filter
from django.conf import settings
......
......@@ -36,10 +36,24 @@ class DatabaseCursor(object):
def __init__(self, ldap_connection):
self.connection = ldap_connection
def __enter__(self):
return self
def __exit__(self):
pass
def close(self):
pass
class DatabaseFeatures(BaseDatabaseFeatures):
can_use_chunked_reads = False
supports_transactions = False
supports_column_check_constraints = False
supports_table_check_constraints = False
supports_ignore_conflicts = False
uses_savepoints = False
supports_partial_indexes = False
def __init__(self, connection):
self.connection = connection
......@@ -59,6 +73,9 @@ class DatabaseOperations(BaseDatabaseOperations):
def no_limit_value(self):
return -1
def sql_flush(self, style, tables, sequences, allow_cascade=False):
return []
class DatabaseValidation(BaseDatabaseValidation):
pass
......@@ -214,7 +231,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# django >= 1.4
self.validate_thread_sharing()
if self.connection is not None:
self.connection.unbind_s()
if hasattr(self.connection, '_l'):
self.connection.unbind_s()
self.connection = None
def get_connection_params(self):
......@@ -281,8 +299,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
def _commit(self):
pass
def _cursor(self):
self.ensure_connection()
def create_cursor(self, name=None):
return DatabaseCursor(self.connection)
def _rollback(self):
......@@ -292,57 +309,62 @@ class DatabaseWrapper(BaseDatabaseWrapper):
pass
def add_s(self, dn, modlist):
cursor = self._cursor()
return cursor.connection.add_s(dn, modlist)
with self.cursor() as cursor:
return cursor.connection.add_s(dn, modlist)
def delete_s(self, dn):
cursor = self._cursor()
return cursor.connection.delete_s(dn)
with self.cursor() as cursor:
return cursor.connection.delete_s(dn)
def modify_s(self, dn, modlist):
cursor = self._cursor()
return cursor.connection.modify_s(dn, modlist)
with self.cursor() as cursor:
return cursor.connection.modify_s(dn, modlist)
def rename_s(self, dn, newrdn):
cursor = self._cursor()
return cursor.connection.rename_s(dn, newrdn)
with self.cursor() as cursor:
return cursor.connection.rename_s(dn, newrdn)
def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None):
cursor = self._cursor()
query_timeout = cursor.connection.timeout
# Request pagination; don't fail if the server doesn't support it.
ldap_control = ldap.controls.SimplePagedResultsControl(
criticality=False,
size=self.page_size,
cookie='',
)
# Fetch results
page = 0
while True:
msgid = cursor.connection.search_ext(
base=base,
scope=scope,
filterstr=filterstr,
attrlist=attrlist,
serverctrls=[ldap_control],
timeout=query_timeout,
with self.cursor() as cursor:
query_timeout = cursor.connection.timeout
# Request pagination; don't fail if the server doesn't support it.
ldap_control = ldap.controls.SimplePagedResultsControl(
criticality=False,
size=self.page_size,
cookie='',
)
_res_type, results, _res_msgid, server_controls = cursor.connection.result3(msgid, timeout=query_timeout)
page_controls = [ctrl for ctrl in server_controls if ctrl.controlType == ldap.CONTROL_PAGEDRESULTS]
for dn, attrs in results:
# skip referrals
if dn is not None:
yield dn, attrs
page_control = page_controls[0]
page += 1
if page_control.cookie:
ldap_control.cookie = page_control.cookie
else:
# End of pages
break
# Fetch results
page = 0
while True:
msgid = cursor.connection.search_ext(
base=base,
scope=scope,
filterstr=filterstr,
attrlist=attrlist,
serverctrls=[ldap_control],
timeout=query_timeout,
)
_res_type, results, _res_msgid, server_controls = cursor.connection.result3(
msgid,
timeout=query_timeout,
)
page_controls = [ctrl for ctrl in server_controls if ctrl.controlType == ldap.CONTROL_PAGEDRESULTS]
for dn, attrs in results:
# skip referrals
if dn is not None:
yield dn, attrs
page_control = page_controls[0]
page += 1
if page_control.cookie:
ldap_control.cookie = page_control.cookie
else:
# End of pages
break
......@@ -230,8 +230,8 @@ class SQLCompiler(compiler.SQLCompiler):
# need but there is probably no other options as we can't perform
# ordering server side.
if (self.query.low_mark and pos < self.query.low_mark) or \
(self.query.high_mark is not None and
pos >= self.query.high_mark):
(self.query.high_mark is not None
and pos >= self.query.high_mark):
pos += 1
continue
row = []
......
......@@ -30,6 +30,8 @@ class Model(django.db.models.base.Model):
def __init__(self, *args, **kwargs):
super(Model, self).__init__(*args, **kwargs)
self._saved_dn = self.dn
if self.dn:
self.base_dn = self.dn.split(',', 1)[1]
def build_rdn(self):
"""
......
......@@ -212,6 +212,21 @@ FloatField.register_lookup(LteLookup)
FloatField.register_lookup(InLookup)
class BooleanField(LdapFieldMixin, fields.BooleanField):
def from_ldap(self, value, connection):
if len(value) == 0:
return None if self.null else False
else:
return value[0].upper() == b'TRUE'
def get_prep_value(self, value):
value = super(BooleanField, self).get_prep_value(value)
return 'TRUE' if value else 'FALSE'
BooleanField.register_lookup(ExactLookup)
class ListField(LdapFieldMixin, fields.Field):
multi_valued_field = True
......
......@@ -184,6 +184,23 @@ class WhereTestCase(TestCase):
where.add(self._build_lookup("uid", 'lte', 1.2, field=fields.FloatField), AND)
self.assertEqual(self._where_as_ldap(where), "(uid<=1.2)")
def test_boolean_field(self):
where = WhereNode()
where.add(self._build_lookup("isSuperuser", 'exact', True, field=fields.BooleanField), AND)
self.assertEqual(self._where_as_ldap(where), "(isSuperuser=TRUE)")
where = WhereNode()
where.add(self._build_lookup("isSuperuser", 'exact', False, field=fields.BooleanField), AND)
self.assertEqual(self._where_as_ldap(where), "(isSuperuser=FALSE)")
where = WhereNode()
where.add(self._build_lookup("isSuperuser", 'exact', 1, field=fields.BooleanField), AND)
self.assertEqual(self._where_as_ldap(where), "(isSuperuser=TRUE)")
where = WhereNode()
where.add(self._build_lookup("isSuperuser", 'exact', 0, field=fields.BooleanField), AND)
self.assertEqual(self._where_as_ldap(where), "(isSuperuser=FALSE)")
def test_list_field_contains(self):
where = WhereNode()
where.add(self._build_lookup("memberUid", 'contains', 'foouser', field=fields.ListField), AND)
......
......@@ -4,6 +4,6 @@
from __future__ import unicode_literals
__version__ = '1.3.0'
__version__ = '1.4.0'
VERSION = __version__
[tox]
envlist = py{27,34,35,36}-django111, py{34,35,36,37}-django20, py{35,36,37}-django21, lint
envlist = py{27,34,35,36,37}-django111, py{35,36,37}-django21, py{35,36,37}-django22, lint
[testenv]
deps =
-rrequirements_test.txt
django111: Django>=1.11,<1.12
django20: Django>=2.0,<2.1
django21: Django>=2.1,<2.2
django22: Django>=2.2,<2.3
whitelist_externals = make
commands = make test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment