added ldap3 lib and ldap3 backend
This commit is contained in:
parent
00c10dbaa8
commit
95b1878141
82
application/ldap_backend.py
Normal file
82
application/ldap_backend.py
Normal file
@ -0,0 +1,82 @@
|
||||
import logging
|
||||
import traceback
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import check_password
|
||||
from django.contrib.auth.models import User
|
||||
from ldap3 import Server, Connection, ALL, NTLM, ALL_ATTRIBUTES
|
||||
from ldap3.core.exceptions import LDAPSocketOpenError
|
||||
import mysite.settings
|
||||
|
||||
class LdapBackend(object):
|
||||
"""
|
||||
Authenticate against a LDAP directory.
|
||||
"""
|
||||
|
||||
log = logging.getLogger('mysite')
|
||||
|
||||
def check_login(self, username, password):
|
||||
server = Server(mysite.settings.LDAP_SERVER, connect_timeout=8)
|
||||
qualified_user = mysite.settings.LDAP_DOMAIN + '\\' + username
|
||||
|
||||
conn = Connection(server, qualified_user, password=password, authentication=NTLM)
|
||||
|
||||
try:
|
||||
conn.bind()
|
||||
except LDAPSocketOpenError:
|
||||
# LDAP Server nicht erreichbar
|
||||
self.log.info("LDAP check_login: Server not reachable.")
|
||||
return None
|
||||
except:
|
||||
var = traceback.format_exc()
|
||||
self.log.info("LDAP check_login(bind): Unexpected Error %s" % var)
|
||||
return None
|
||||
|
||||
result = None
|
||||
|
||||
try:
|
||||
if conn.extend.standard.who_am_i() != None:
|
||||
conn.search(
|
||||
search_base='DC=' + mysite.settings.LDAP_DOMAIN + ',DC=fh-nuernberg,DC=de',
|
||||
search_filter='(&(objectclass=user)(CN=' + username + '))', attributes=ALL_ATTRIBUTES)
|
||||
info = conn.entries[0]
|
||||
result = {'lastname' : str(info.sn),
|
||||
'givenname' : str(info.givenName),
|
||||
'login' : str(info.cn),
|
||||
'department' : str(info.department),
|
||||
'role' : str(info.description)}
|
||||
self.log.info("LDAP check_login: %s" % result)
|
||||
except:
|
||||
var = traceback.format_exc()
|
||||
self.log.info("LDAP check_login: Unexpected Error %s" % var)
|
||||
conn.unbind()
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def authenticate(self, request, username=None, password=None):
|
||||
ldap_user = self.check_login(username,password)
|
||||
if ldap_user:
|
||||
# {'lastname': 'Hofmann', 'givenname': 'Oliver', 'login': 'hofmannol', 'department': 'EFI', 'role': 'PF'}
|
||||
# {'lastname': 'Wimmer', 'givenname': 'Martin', 'login': 'wimmerma', 'department': 'EFI', 'role': 'MA'}
|
||||
# {'lastname': 'Mueller', 'givenname': 'Vincent', 'login': 'muellervi56608', 'department': 'EFI', 'role': 'ST'}
|
||||
# {'lastname': 'Poehlau', 'givenname': 'Frank', 'login': 'poehlaufr', 'department': 'EFI', 'role': 'PF'}
|
||||
try:
|
||||
user = User.objects.get(username=ldap_user['login'])
|
||||
except User.DoesNotExist:
|
||||
self.log.info("LDAP authenticate: create new user %s" % ldap_user['login'])
|
||||
user = User(username=ldap_user['login'])
|
||||
user.first_name = ldap_user['givenname']
|
||||
user.last_name = ldap_user['lastname']
|
||||
user.is_staff = (ldap_user['role'] != 'ST')
|
||||
user.is_superuser = False
|
||||
user.save()
|
||||
return user
|
||||
return None
|
||||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
@ -164,6 +164,6 @@ if DEVELOPMENT:
|
||||
else:
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'medinf.ldap_backend.LdapBackend',
|
||||
'mysite.ldap_backend.LdapBackend',
|
||||
]
|
||||
print(" --- Live stage --- ")
|
@ -15,16 +15,16 @@ Including another URLconf
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.conf.urls import include, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
from django.contrib.auth import views
|
||||
import application.views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', TemplateView.as_view(template_name="index.html")),
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^navlogin/', application.views.navlogin, name='navlogin'),
|
||||
url(r'^accounts/login/$', views.login, name='login'),
|
||||
url(r'^accounts/logout/$', views.logout,
|
||||
name='logout', kwargs={'next_page': '/'}),
|
||||
url(r'', include('application.urls')),
|
||||
url(r'^accounts/', include('django.contrib.auth.urls')),
|
||||
]
|
12
thesisenv/bin/autopep8
Executable file
12
thesisenv/bin/autopep8
Executable file
@ -0,0 +1,12 @@
|
||||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: 'autopep8==1.3.5','console_scripts','autopep8'
|
||||
__requires__ = 'autopep8==1.3.5'
|
||||
import re
|
||||
import sys
|
||||
from pkg_resources import load_entry_point
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(
|
||||
load_entry_point('autopep8==1.3.5', 'console_scripts', 'autopep8')()
|
||||
)
|
11
thesisenv/bin/pycodestyle
Executable file
11
thesisenv/bin/pycodestyle
Executable file
@ -0,0 +1,11 @@
|
||||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pycodestyle import _main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(_main())
|
BIN
thesisenv/lib/python3.6/site-packages/_ldap.cpython-36m-darwin.so
Executable file
BIN
thesisenv/lib/python3.6/site-packages/_ldap.cpython-36m-darwin.so
Executable file
Binary file not shown.
@ -0,0 +1,385 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: autopep8
|
||||
Version: 1.3.5
|
||||
Summary: A tool that automatically formats Python code to conform to the PEP 8 style guide
|
||||
Home-page: https://github.com/hhatto/autopep8
|
||||
Author: Hideo Hattori
|
||||
Author-email: hhatto.jp@gmail.com
|
||||
License: Expat License
|
||||
Description: ========
|
||||
autopep8
|
||||
========
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/autopep8.svg
|
||||
:target: https://pypi.python.org/pypi/autopep8
|
||||
:alt: PyPI Version
|
||||
|
||||
.. image:: https://travis-ci.org/hhatto/autopep8.svg?branch=master
|
||||
:target: https://travis-ci.org/hhatto/autopep8
|
||||
:alt: Build status
|
||||
|
||||
autopep8 automatically formats Python code to conform to the `PEP 8`_ style
|
||||
guide. It uses the pycodestyle_ utility to determine what parts of the code
|
||||
needs to be formatted. autopep8 is capable of fixing most of the formatting
|
||||
issues_ that can be reported by pycodestyle.
|
||||
|
||||
.. _PEP 8: https://www.python.org/dev/peps/pep-0008/
|
||||
.. _issues: https://pycodestyle.readthedocs.org/en/latest/intro.html#error-codes
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
From pip::
|
||||
|
||||
$ pip install --upgrade autopep8
|
||||
|
||||
Consider using the ``--user`` option_.
|
||||
|
||||
.. _option: https://pip.pypa.io/en/latest/user_guide/#user-installs
|
||||
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
autopep8 requires pycodestyle_.
|
||||
|
||||
.. _pycodestyle: https://github.com/PyCQA/pycodestyle
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To modify a file in place (with aggressive level 2)::
|
||||
|
||||
$ autopep8 --in-place --aggressive --aggressive <filename>
|
||||
|
||||
Before running autopep8.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import math, sys;
|
||||
|
||||
def example1():
|
||||
####This is a long comment. This should be wrapped to fit within 72 characters.
|
||||
some_tuple=( 1,2, 3,'a' );
|
||||
some_variable={'long':'Long code lines should be wrapped within 79 characters.',
|
||||
'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
|
||||
'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
|
||||
20,300,40000,500000000,60000000000000000]}}
|
||||
return (some_tuple, some_variable)
|
||||
def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
|
||||
class Example3( object ):
|
||||
def __init__ ( self, bar ):
|
||||
#Comments should have a space after the hash.
|
||||
if bar : bar+=1; bar=bar* bar ; return bar
|
||||
else:
|
||||
some_string = """
|
||||
Indentation in multiline strings should not be touched.
|
||||
Only actual code should be reindented.
|
||||
"""
|
||||
return (sys.path, some_string)
|
||||
|
||||
After running autopep8.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
|
||||
def example1():
|
||||
# This is a long comment. This should be wrapped to fit within 72
|
||||
# characters.
|
||||
some_tuple = (1, 2, 3, 'a')
|
||||
some_variable = {
|
||||
'long': 'Long code lines should be wrapped within 79 characters.',
|
||||
'other': [
|
||||
math.pi,
|
||||
100,
|
||||
200,
|
||||
300,
|
||||
9876543210,
|
||||
'This is a long string that goes on'],
|
||||
'more': {
|
||||
'inner': 'This whole logical line should be wrapped.',
|
||||
some_tuple: [
|
||||
1,
|
||||
20,
|
||||
300,
|
||||
40000,
|
||||
500000000,
|
||||
60000000000000000]}}
|
||||
return (some_tuple, some_variable)
|
||||
|
||||
|
||||
def example2(): return ('' in {'f': 2}) in {'has_key() is deprecated': True}
|
||||
|
||||
|
||||
class Example3(object):
|
||||
|
||||
def __init__(self, bar):
|
||||
# Comments should have a space after the hash.
|
||||
if bar:
|
||||
bar += 1
|
||||
bar = bar * bar
|
||||
return bar
|
||||
else:
|
||||
some_string = """
|
||||
Indentation in multiline strings should not be touched.
|
||||
Only actual code should be reindented.
|
||||
"""
|
||||
return (sys.path, some_string)
|
||||
|
||||
Options::
|
||||
|
||||
usage: autopep8 [-h] [--version] [-v] [-d] [-i] [--global-config filename]
|
||||
[--ignore-local-config] [-r] [-j n] [-p n] [-a]
|
||||
[--experimental] [--exclude globs] [--list-fixes]
|
||||
[--ignore errors] [--select errors] [--max-line-length n]
|
||||
[--line-range line line]
|
||||
[files [files ...]]
|
||||
|
||||
Automatically formats Python code to conform to the PEP 8 style guide.
|
||||
|
||||
positional arguments:
|
||||
files files to format or '-' for standard in
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--version show program's version number and exit
|
||||
-v, --verbose print verbose messages; multiple -v result in more
|
||||
verbose messages
|
||||
-d, --diff print the diff for the fixed source
|
||||
-i, --in-place make changes to files in place
|
||||
--global-config filename
|
||||
path to a global pep8 config file; if this file does
|
||||
not exist then this is ignored (default:
|
||||
~/.config/pep8)
|
||||
--ignore-local-config
|
||||
don't look for and apply local config files; if not
|
||||
passed, defaults are updated with any config files in
|
||||
the project's root directory
|
||||
-r, --recursive run recursively over directories; must be used with
|
||||
--in-place or --diff
|
||||
-j n, --jobs n number of parallel jobs; match CPU count if value is
|
||||
less than 1
|
||||
-p n, --pep8-passes n
|
||||
maximum number of additional pep8 passes (default:
|
||||
infinite)
|
||||
-a, --aggressive enable non-whitespace changes; multiple -a result in
|
||||
more aggressive changes
|
||||
--experimental enable experimental fixes
|
||||
--exclude globs exclude file/directory names that match these comma-
|
||||
separated globs
|
||||
--list-fixes list codes for fixes; used by --ignore and --select
|
||||
--ignore errors do not fix these errors/warnings (default: E24)
|
||||
--select errors fix only these errors/warnings (e.g. E4,W)
|
||||
--max-line-length n set maximum allowed line length (default: 79)
|
||||
--line-range line line, --range line line
|
||||
only fix errors found within this inclusive range of
|
||||
line numbers (e.g. 1 99); line numbers are indexed at
|
||||
1
|
||||
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
autopep8 fixes the following issues_ reported by pycodestyle_::
|
||||
|
||||
E101 - Reindent all lines.
|
||||
E11 - Fix indentation. (not include E112 and E113)
|
||||
E121 - Fix indentation to be a multiple of four.
|
||||
E122 - Add absent indentation for hanging indentation.
|
||||
E123 - Align closing bracket to match opening bracket.
|
||||
E124 - Align closing bracket to match visual indentation.
|
||||
E125 - Indent to distinguish line from next logical line.
|
||||
E126 - Fix over-indented hanging indentation.
|
||||
E127 - Fix visual indentation.
|
||||
E128 - Fix visual indentation.
|
||||
E20 - Remove extraneous whitespace.
|
||||
E211 - Remove extraneous whitespace.
|
||||
E22 - Fix extraneous whitespace around keywords.
|
||||
E224 - Remove extraneous whitespace around operator.
|
||||
E226 - Fix missing whitespace around arithmetic operator.
|
||||
E227 - Fix missing whitespace around bitwise/shift operator.
|
||||
E228 - Fix missing whitespace around modulo operator.
|
||||
E231 - Add missing whitespace.
|
||||
E241 - Fix extraneous whitespace around keywords.
|
||||
E242 - Remove extraneous whitespace around operator.
|
||||
E251 - Remove whitespace around parameter '=' sign.
|
||||
E26 - Fix spacing after comment hash for inline comments.
|
||||
E265 - Fix spacing after comment hash for block comments.
|
||||
E27 - Fix extraneous whitespace around keywords.
|
||||
E301 - Add missing blank line.
|
||||
E302 - Add missing 2 blank lines.
|
||||
E303 - Remove extra blank lines.
|
||||
E304 - Remove blank line following function decorator.
|
||||
E306 - Expected 1 blank line before a nested definition
|
||||
E401 - Put imports on separate lines.
|
||||
E501 - Try to make lines fit within --max-line-length characters.
|
||||
E502 - Remove extraneous escape of newline.
|
||||
E701 - Put colon-separated compound statement on separate lines.
|
||||
E70 - Put semicolon-separated compound statement on separate lines.
|
||||
E711 - Fix comparison with None.
|
||||
E712 - Fix comparison with boolean.
|
||||
E721 - Use "isinstance()" instead of comparing types directly.
|
||||
E722 - Fix bare except.
|
||||
W291 - Remove trailing whitespace.
|
||||
W292 - Add a single newline at the end of the file.
|
||||
W293 - Remove trailing whitespace on blank line.
|
||||
W391 - Remove trailing blank lines.
|
||||
W601 - Use "in" rather than "has_key()".
|
||||
W602 - Fix deprecated form of raising exception.
|
||||
W603 - Use "!=" instead of "<>"
|
||||
W604 - Use "repr()" instead of backticks.
|
||||
W690 - Fix various deprecated code (via lib2to3).
|
||||
|
||||
autopep8 also fixes some issues not found by pycodestyle_.
|
||||
|
||||
- Correct deprecated or non-idiomatic Python code (via ``lib2to3``). Use this
|
||||
for making Python 2.7 code more compatible with Python 3. (This is triggered
|
||||
if ``W690`` is enabled.)
|
||||
- Normalize files with mixed line endings.
|
||||
- Put a blank line between a class docstring and its first method
|
||||
declaration. (Enabled with ``E301``.)
|
||||
- Remove blank lines between a function declaration and its docstring. (Enabled
|
||||
with ``E303``.)
|
||||
|
||||
autopep8 avoids fixing some issues found by pycodestyle_.
|
||||
|
||||
- ``E112``/``E113`` for non comments are reports of bad indentation that break
|
||||
syntax rules. These should not be modified at all.
|
||||
- ``E265``, which refers to spacing after comment hash, is ignored if the
|
||||
comment looks like code. autopep8 avoids modifying these since they are not
|
||||
real comments. If you really want to get rid of the pycodestyle_ warning,
|
||||
consider just removing the commented-out code. (This can be automated via
|
||||
eradicate_.)
|
||||
|
||||
.. _eradicate: https://github.com/myint/eradicate
|
||||
|
||||
|
||||
More advanced usage
|
||||
===================
|
||||
|
||||
By default autopep8 only makes whitespace changes. Thus, by default, it does
|
||||
not fix ``E711`` and ``E712``. (Changing ``x == None`` to ``x is None`` may
|
||||
change the meaning of the program if ``x`` has its ``__eq__`` method
|
||||
overridden.) Nor does it correct deprecated code ``W6``. To enable these
|
||||
more aggressive fixes, use the ``--aggressive`` option::
|
||||
|
||||
$ autopep8 --aggressive <filename>
|
||||
|
||||
Use multiple ``--aggressive`` to increase the aggressiveness level. For
|
||||
example, ``E712`` requires aggressiveness level 2 (since ``x == True`` could be
|
||||
changed to either ``x`` or ``x is True``, but autopep8 chooses the former).
|
||||
|
||||
``--aggressive`` will also shorten lines more aggressively. It will also remove
|
||||
trailing whitespace more aggressively. (Usually, we don't touch trailing
|
||||
whitespace in docstrings and other multiline strings. And to do even more
|
||||
aggressive changes to docstrings, use docformatter_.)
|
||||
|
||||
.. _docformatter: https://github.com/myint/docformatter
|
||||
|
||||
To enable only a subset of the fixes, use the ``--select`` option. For example,
|
||||
to fix various types of indentation issues::
|
||||
|
||||
$ autopep8 --select=E1,W1 <filename>
|
||||
|
||||
Similarly, to just fix deprecated code::
|
||||
|
||||
$ autopep8 --aggressive --select=W6 <filename>
|
||||
|
||||
The above is useful when trying to port a single code base to work with both
|
||||
Python 2 and Python 3 at the same time.
|
||||
|
||||
If the file being fixed is large, you may want to enable verbose progress
|
||||
messages::
|
||||
|
||||
$ autopep8 -v <filename>
|
||||
|
||||
|
||||
Use as a module
|
||||
===============
|
||||
|
||||
The simplest way of using autopep8 as a module is via the ``fix_code()``
|
||||
function:
|
||||
|
||||
>>> import autopep8
|
||||
>>> autopep8.fix_code('x= 123\n')
|
||||
'x = 123\n'
|
||||
|
||||
Or with options:
|
||||
|
||||
>>> import autopep8
|
||||
>>> autopep8.fix_code('x.has_key(y)\n',
|
||||
... options={'aggressive': 1})
|
||||
'y in x\n'
|
||||
>>> autopep8.fix_code('print( 123 )\n',
|
||||
... options={'ignore': ['E']})
|
||||
'print( 123 )\n'
|
||||
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
Test cases are in ``test/test_autopep8.py``. They can be run directly via
|
||||
``python test/test_autopep8.py`` or via tox_. The latter is useful for
|
||||
testing against multiple Python interpreters. (We currently test against
|
||||
CPython versions 2.7, 3.4, 3.5 and 3.6. We also test against PyPy.)
|
||||
|
||||
.. _`tox`: https://pypi.python.org/pypi/tox
|
||||
|
||||
Broad spectrum testing is available via ``test/acid.py``. This script runs
|
||||
autopep8 against Python code and checks for correctness and completeness of the
|
||||
code fixes. It can check that the bytecode remains identical.
|
||||
``test/acid_pypi.py`` makes use of ``acid.py`` to test against the latest
|
||||
released packages on PyPI.
|
||||
|
||||
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
``pkg_resources.DistributionNotFound``
|
||||
--------------------------------------
|
||||
|
||||
If you are using an ancient version of ``setuptools``, you might encounter
|
||||
``pkg_resources.DistributionNotFound`` when trying to run ``autopep8``. Try
|
||||
upgrading ``setuptools`` to workaround this ``setuptools`` problem::
|
||||
|
||||
$ pip install --upgrade setuptools
|
||||
|
||||
Use ``sudo`` if you are installing to the system.
|
||||
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
* PyPI_
|
||||
* GitHub_
|
||||
* `Travis CI`_
|
||||
* Coveralls_
|
||||
|
||||
.. _PyPI: https://pypi.python.org/pypi/autopep8/
|
||||
.. _GitHub: https://github.com/hhatto/autopep8
|
||||
.. _`Travis CI`: https://travis-ci.org/hhatto/autopep8
|
||||
.. _`Coveralls`: https://coveralls.io/r/hhatto/autopep8
|
||||
|
||||
Keywords: automation,pep8,format,pycodestyle
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Software Development :: Quality Assurance
|
@ -0,0 +1,25 @@
|
||||
AUTHORS.rst
|
||||
LICENSE
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
autopep8.py
|
||||
setup.cfg
|
||||
setup.py
|
||||
autopep8.egg-info/PKG-INFO
|
||||
autopep8.egg-info/SOURCES.txt
|
||||
autopep8.egg-info/dependency_links.txt
|
||||
autopep8.egg-info/entry_points.txt
|
||||
autopep8.egg-info/not-zip-safe
|
||||
autopep8.egg-info/requires.txt
|
||||
autopep8.egg-info/top_level.txt
|
||||
test/__init__.py
|
||||
test/bad_encoding.py
|
||||
test/bad_encoding2.py
|
||||
test/e101_example.py
|
||||
test/example.py
|
||||
test/example_with_reduce.py
|
||||
test/iso_8859_1.py
|
||||
test/test_autopep8.py
|
||||
test/test_suite.py
|
||||
test/fake_configuration/.pep8
|
||||
test/fake_pycodestyle_configuration/tox.ini
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
autopep8 = autopep8:main
|
||||
|
@ -0,0 +1,10 @@
|
||||
../../../../bin/autopep8
|
||||
../__pycache__/autopep8.cpython-36.pyc
|
||||
../autopep8.py
|
||||
PKG-INFO
|
||||
SOURCES.txt
|
||||
dependency_links.txt
|
||||
entry_points.txt
|
||||
not-zip-safe
|
||||
requires.txt
|
||||
top_level.txt
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
pycodestyle>=2.3
|
@ -0,0 +1 @@
|
||||
autopep8
|
3961
thesisenv/lib/python3.6/site-packages/autopep8.py
Normal file
3961
thesisenv/lib/python3.6/site-packages/autopep8.py
Normal file
File diff suppressed because it is too large
Load Diff
105
thesisenv/lib/python3.6/site-packages/ldap/__init__.py
Normal file
105
thesisenv/lib/python3.6/site-packages/ldap/__init__.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""
|
||||
ldap - base module
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
# This is also the overall release version number
|
||||
|
||||
from ldap.pkginfo import __version__, __author__, __license__
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __debug__:
|
||||
# Tracing is only supported in debugging mode
|
||||
import atexit
|
||||
import traceback
|
||||
_trace_level = int(os.environ.get("PYTHON_LDAP_TRACE_LEVEL", 0))
|
||||
_trace_file = os.environ.get("PYTHON_LDAP_TRACE_FILE")
|
||||
if _trace_file is None:
|
||||
_trace_file = sys.stderr
|
||||
else:
|
||||
_trace_file = open(_trace_file, 'a')
|
||||
atexit.register(_trace_file.close)
|
||||
_trace_stack_limit = None
|
||||
|
||||
import _ldap
|
||||
assert _ldap.__version__==__version__, \
|
||||
ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__))
|
||||
from _ldap import *
|
||||
# call into libldap to initialize it right now
|
||||
LIBLDAP_API_INFO = _ldap.get_option(_ldap.OPT_API_INFO)
|
||||
|
||||
OPT_NAMES_DICT = {}
|
||||
for k,v in vars(_ldap).items():
|
||||
if k.startswith('OPT_'):
|
||||
OPT_NAMES_DICT[v]=k
|
||||
|
||||
class DummyLock:
|
||||
"""Define dummy class with methods compatible to threading.Lock"""
|
||||
def __init__(self):
|
||||
pass
|
||||
def acquire(self):
|
||||
pass
|
||||
def release(self):
|
||||
pass
|
||||
|
||||
try:
|
||||
# Check if Python installation was build with thread support
|
||||
import thread
|
||||
except ImportError:
|
||||
LDAPLockBaseClass = DummyLock
|
||||
else:
|
||||
import threading
|
||||
LDAPLockBaseClass = threading.Lock
|
||||
|
||||
|
||||
class LDAPLock:
|
||||
"""
|
||||
Mainly a wrapper class to log all locking events.
|
||||
Note that this cumbersome approach with _lock attribute was taken
|
||||
since threading.Lock is not suitable for sub-classing.
|
||||
"""
|
||||
_min_trace_level = 3
|
||||
|
||||
def __init__(self,lock_class=None,desc=''):
|
||||
"""
|
||||
lock_class
|
||||
Class compatible to threading.Lock
|
||||
desc
|
||||
Description shown in debug log messages
|
||||
"""
|
||||
self._desc = desc
|
||||
self._lock = (lock_class or LDAPLockBaseClass)()
|
||||
|
||||
def acquire(self):
|
||||
if __debug__:
|
||||
global _trace_level
|
||||
if _trace_level>=self._min_trace_level:
|
||||
_trace_file.write('***%s.acquire() %s %s\n' % (self.__class__.__name__,repr(self),self._desc))
|
||||
return self._lock.acquire()
|
||||
|
||||
def release(self):
|
||||
if __debug__:
|
||||
global _trace_level
|
||||
if _trace_level>=self._min_trace_level:
|
||||
_trace_file.write('***%s.release() %s %s\n' % (self.__class__.__name__,repr(self),self._desc))
|
||||
return self._lock.release()
|
||||
|
||||
|
||||
# Create module-wide lock for serializing all calls into underlying LDAP lib
|
||||
_ldap_module_lock = LDAPLock(desc='Module wide')
|
||||
|
||||
from ldap.functions import initialize,get_option,set_option,escape_str,strf_secs,strp_secs
|
||||
|
||||
from ldap.ldapobject import NO_UNIQUE_ENTRY, LDAPBytesWarning
|
||||
|
||||
from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str
|
||||
del str2dn
|
||||
del dn2str
|
||||
|
||||
# More constants
|
||||
|
||||
# For compatibility of 2.3 and 2.4 OpenLDAP API
|
||||
OPT_DIAGNOSTIC_MESSAGE = OPT_ERROR_STRING
|
15
thesisenv/lib/python3.6/site-packages/ldap/async.py
Normal file
15
thesisenv/lib/python3.6/site-packages/ldap/async.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
ldap.asyncsearch - handle async LDAP search operations
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from ldap.asyncsearch import *
|
||||
from ldap.asyncsearch import __version__
|
||||
|
||||
warnings.warn(
|
||||
"'ldap.async module' is deprecated, import 'ldap.asyncsearch' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
284
thesisenv/lib/python3.6/site-packages/ldap/asyncsearch.py
Normal file
284
thesisenv/lib/python3.6/site-packages/ldap/asyncsearch.py
Normal file
@ -0,0 +1,284 @@
|
||||
"""
|
||||
ldap.asyncsearch - handle async LDAP search operations
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import ldap
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
import ldif
|
||||
|
||||
SEARCH_RESULT_TYPES = {
|
||||
ldap.RES_SEARCH_ENTRY,
|
||||
ldap.RES_SEARCH_RESULT,
|
||||
ldap.RES_SEARCH_REFERENCE,
|
||||
}
|
||||
|
||||
ENTRY_RESULT_TYPES = {
|
||||
ldap.RES_SEARCH_ENTRY,
|
||||
ldap.RES_SEARCH_RESULT,
|
||||
}
|
||||
|
||||
|
||||
class WrongResultType(Exception):
|
||||
|
||||
def __init__(self,receivedResultType,expectedResultTypes):
|
||||
self.receivedResultType = receivedResultType
|
||||
self.expectedResultTypes = expectedResultTypes
|
||||
Exception.__init__(self)
|
||||
|
||||
def __str__(self):
|
||||
return 'Received wrong result type %s (expected one of %s).' % (
|
||||
self.receivedResultType,
|
||||
', '.join(self.expectedResultTypes),
|
||||
)
|
||||
|
||||
|
||||
class AsyncSearchHandler:
|
||||
"""
|
||||
Class for stream-processing LDAP search results
|
||||
|
||||
Arguments:
|
||||
|
||||
l
|
||||
LDAPObject instance
|
||||
"""
|
||||
|
||||
def __init__(self,l):
|
||||
self._l = l
|
||||
self._msgId = None
|
||||
self._afterFirstResult = 1
|
||||
|
||||
def startSearch(
|
||||
self,
|
||||
searchRoot,
|
||||
searchScope,
|
||||
filterStr,
|
||||
attrList=None,
|
||||
attrsOnly=0,
|
||||
timeout=-1,
|
||||
sizelimit=0,
|
||||
serverctrls=None,
|
||||
clientctrls=None
|
||||
):
|
||||
"""
|
||||
searchRoot
|
||||
See parameter base of method LDAPObject.search()
|
||||
searchScope
|
||||
See parameter scope of method LDAPObject.search()
|
||||
filterStr
|
||||
See parameter filter of method LDAPObject.search()
|
||||
attrList=None
|
||||
See parameter attrlist of method LDAPObject.search()
|
||||
attrsOnly
|
||||
See parameter attrsonly of method LDAPObject.search()
|
||||
timeout
|
||||
Maximum time the server shall use for search operation
|
||||
sizelimit
|
||||
Maximum number of entries a server should return
|
||||
(request client-side limit)
|
||||
serverctrls
|
||||
list of server-side LDAP controls
|
||||
clientctrls
|
||||
list of client-side LDAP controls
|
||||
"""
|
||||
self._msgId = self._l.search_ext(
|
||||
searchRoot,searchScope,filterStr,
|
||||
attrList,attrsOnly,serverctrls,clientctrls,timeout,sizelimit
|
||||
)
|
||||
self._afterFirstResult = 1
|
||||
return # startSearch()
|
||||
|
||||
def preProcessing(self):
|
||||
"""
|
||||
Do anything you want after starting search but
|
||||
before receiving and processing results
|
||||
"""
|
||||
|
||||
def afterFirstResult(self):
|
||||
"""
|
||||
Do anything you want right after successfully receiving but before
|
||||
processing first result
|
||||
"""
|
||||
|
||||
def postProcessing(self):
|
||||
"""
|
||||
Do anything you want after receiving and processing all results
|
||||
"""
|
||||
|
||||
def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1):
|
||||
"""
|
||||
ignoreResultsNumber
|
||||
Don't process the first ignoreResultsNumber results.
|
||||
processResultsCount
|
||||
If non-zero this parameters indicates the number of results
|
||||
processed is limited to processResultsCount.
|
||||
timeout
|
||||
See parameter timeout of ldap.LDAPObject.result()
|
||||
"""
|
||||
self.preProcessing()
|
||||
result_counter = 0
|
||||
end_result_counter = ignoreResultsNumber+processResultsCount
|
||||
go_ahead = 1
|
||||
partial = 0
|
||||
self.beginResultsDropped = 0
|
||||
self.endResultBreak = result_counter
|
||||
try:
|
||||
result_type,result_list = None,None
|
||||
while go_ahead:
|
||||
while result_type is None and not result_list:
|
||||
result_type,result_list,result_msgid,result_serverctrls = self._l.result3(self._msgId,0,timeout)
|
||||
if self._afterFirstResult:
|
||||
self.afterFirstResult()
|
||||
self._afterFirstResult = 0
|
||||
if not result_list:
|
||||
break
|
||||
if result_type not in SEARCH_RESULT_TYPES:
|
||||
raise WrongResultType(result_type,SEARCH_RESULT_TYPES)
|
||||
# Loop over list of search results
|
||||
for result_item in result_list:
|
||||
if result_counter<ignoreResultsNumber:
|
||||
self.beginResultsDropped = self.beginResultsDropped+1
|
||||
elif processResultsCount==0 or result_counter<end_result_counter:
|
||||
self._processSingleResult(result_type,result_item)
|
||||
else:
|
||||
go_ahead = 0 # break-out from while go_ahead
|
||||
partial = 1
|
||||
break # break-out from this for-loop
|
||||
result_counter = result_counter+1
|
||||
result_type,result_list = None,None
|
||||
self.endResultBreak = result_counter
|
||||
finally:
|
||||
if partial and self._msgId!=None:
|
||||
self._l.abandon(self._msgId)
|
||||
self.postProcessing()
|
||||
return partial # processResults()
|
||||
|
||||
def _processSingleResult(self,resultType,resultItem):
|
||||
"""
|
||||
Process single entry
|
||||
|
||||
resultType
|
||||
result type
|
||||
resultItem
|
||||
Single item of a result list
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class List(AsyncSearchHandler):
|
||||
"""
|
||||
Class for collecting all search results.
|
||||
|
||||
This does not seem to make sense in the first place but think
|
||||
of retrieving exactly a certain portion of the available search
|
||||
results.
|
||||
"""
|
||||
|
||||
def __init__(self,l):
|
||||
AsyncSearchHandler.__init__(self,l)
|
||||
self.allResults = []
|
||||
|
||||
def _processSingleResult(self,resultType,resultItem):
|
||||
self.allResults.append((resultType,resultItem))
|
||||
|
||||
|
||||
class Dict(AsyncSearchHandler):
|
||||
"""
|
||||
Class for collecting all search results into a dictionary {dn:entry}
|
||||
"""
|
||||
|
||||
def __init__(self,l):
|
||||
AsyncSearchHandler.__init__(self,l)
|
||||
self.allEntries = {}
|
||||
|
||||
def _processSingleResult(self,resultType,resultItem):
|
||||
if resultType in ENTRY_RESULT_TYPES:
|
||||
# Search continuations are ignored
|
||||
dn,entry = resultItem
|
||||
self.allEntries[dn] = entry
|
||||
|
||||
|
||||
class IndexedDict(Dict):
|
||||
"""
|
||||
Class for collecting all search results into a dictionary {dn:entry}
|
||||
and maintain case-sensitive equality indexes to entries
|
||||
"""
|
||||
|
||||
def __init__(self,l,indexed_attrs=None):
|
||||
Dict.__init__(self,l)
|
||||
self.indexed_attrs = indexed_attrs or ()
|
||||
self.index = {}.fromkeys(self.indexed_attrs,{})
|
||||
|
||||
def _processSingleResult(self,resultType,resultItem):
|
||||
if resultType in ENTRY_RESULT_TYPES:
|
||||
# Search continuations are ignored
|
||||
dn,entry = resultItem
|
||||
self.allEntries[dn] = entry
|
||||
for a in self.indexed_attrs:
|
||||
if a in entry:
|
||||
for v in entry[a]:
|
||||
try:
|
||||
self.index[a][v].append(dn)
|
||||
except KeyError:
|
||||
self.index[a][v] = [ dn ]
|
||||
|
||||
|
||||
class FileWriter(AsyncSearchHandler):
|
||||
"""
|
||||
Class for writing a stream of LDAP search results to a file object
|
||||
|
||||
Arguments:
|
||||
l
|
||||
LDAPObject instance
|
||||
f
|
||||
File object instance where the LDIF data is written to
|
||||
"""
|
||||
|
||||
def __init__(self,l,f,headerStr='',footerStr=''):
|
||||
AsyncSearchHandler.__init__(self,l)
|
||||
self._f = f
|
||||
self.headerStr = headerStr
|
||||
self.footerStr = footerStr
|
||||
|
||||
def preProcessing(self):
|
||||
"""
|
||||
The headerStr is written to output after starting search but
|
||||
before receiving and processing results.
|
||||
"""
|
||||
self._f.write(self.headerStr)
|
||||
|
||||
def postProcessing(self):
|
||||
"""
|
||||
The footerStr is written to output after receiving and
|
||||
processing results.
|
||||
"""
|
||||
self._f.write(self.footerStr)
|
||||
|
||||
|
||||
class LDIFWriter(FileWriter):
|
||||
"""
|
||||
Class for writing a stream LDAP search results to a LDIF file
|
||||
|
||||
Arguments:
|
||||
|
||||
l
|
||||
LDAPObject instance
|
||||
writer_obj
|
||||
Either a file-like object or a ldif.LDIFWriter instance used for output
|
||||
"""
|
||||
|
||||
def __init__(self,l,writer_obj,headerStr='',footerStr=''):
|
||||
if isinstance(writer_obj,ldif.LDIFWriter):
|
||||
self._ldif_writer = writer_obj
|
||||
else:
|
||||
self._ldif_writer = ldif.LDIFWriter(writer_obj)
|
||||
FileWriter.__init__(self,l,self._ldif_writer._output_file,headerStr,footerStr)
|
||||
|
||||
def _processSingleResult(self,resultType,resultItem):
|
||||
if resultType in ENTRY_RESULT_TYPES:
|
||||
# Search continuations are ignored
|
||||
dn,entry = resultItem
|
||||
self._ldif_writer.unparse(dn,entry)
|
100
thesisenv/lib/python3.6/site-packages/ldap/cidict.py
Normal file
100
thesisenv/lib/python3.6/site-packages/ldap/cidict.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""
|
||||
This is a convenience wrapper for dictionaries
|
||||
returned from LDAP servers containing attribute
|
||||
names of variable case.
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
from ldap.compat import IterableUserDict
|
||||
|
||||
|
||||
class cidict(IterableUserDict):
|
||||
"""
|
||||
Case-insensitive but case-respecting dictionary.
|
||||
"""
|
||||
|
||||
def __init__(self,default=None):
|
||||
self._keys = {}
|
||||
IterableUserDict.__init__(self,{})
|
||||
self.update(default or {})
|
||||
|
||||
def __getitem__(self,key):
|
||||
return self.data[key.lower()]
|
||||
|
||||
def __setitem__(self,key,value):
|
||||
lower_key = key.lower()
|
||||
self._keys[lower_key] = key
|
||||
self.data[lower_key] = value
|
||||
|
||||
def __delitem__(self,key):
|
||||
lower_key = key.lower()
|
||||
del self._keys[lower_key]
|
||||
del self.data[lower_key]
|
||||
|
||||
def update(self,dict):
|
||||
for key, value in dict.items():
|
||||
self[key] = value
|
||||
|
||||
def has_key(self,key):
|
||||
return key in self
|
||||
|
||||
def __contains__(self,key):
|
||||
return IterableUserDict.__contains__(self, key.lower())
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.keys())
|
||||
|
||||
def keys(self):
|
||||
return self._keys.values()
|
||||
|
||||
def items(self):
|
||||
result = []
|
||||
for k in self._keys.values():
|
||||
result.append((k,self[k]))
|
||||
return result
|
||||
|
||||
|
||||
def strlist_minus(a,b):
|
||||
"""
|
||||
Return list of all items in a which are not in b (a - b).
|
||||
a,b are supposed to be lists of case-insensitive strings.
|
||||
"""
|
||||
temp = cidict()
|
||||
for elt in b:
|
||||
temp[elt] = elt
|
||||
result = [
|
||||
elt
|
||||
for elt in a
|
||||
if elt not in temp
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
def strlist_intersection(a,b):
|
||||
"""
|
||||
Return intersection of two lists of case-insensitive strings a,b.
|
||||
"""
|
||||
temp = cidict()
|
||||
for elt in a:
|
||||
temp[elt] = elt
|
||||
result = [
|
||||
temp[elt]
|
||||
for elt in b
|
||||
if elt in temp
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
def strlist_union(a,b):
|
||||
"""
|
||||
Return union of two lists of case-insensitive strings a,b.
|
||||
"""
|
||||
temp = cidict()
|
||||
for elt in a:
|
||||
temp[elt] = elt
|
||||
for elt in b:
|
||||
temp[elt] = elt
|
||||
return temp.values()
|
113
thesisenv/lib/python3.6/site-packages/ldap/compat.py
Normal file
113
thesisenv/lib/python3.6/site-packages/ldap/compat.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""Compatibility wrappers for Py2/Py3."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
from UserDict import UserDict, IterableUserDict
|
||||
from urllib import quote
|
||||
from urllib import quote_plus
|
||||
from urllib import unquote as urllib_unquote
|
||||
from urllib import urlopen
|
||||
from urlparse import urlparse
|
||||
|
||||
def unquote(uri):
|
||||
"""Specialized unquote that uses UTF-8 for parsing."""
|
||||
uri = uri.encode('ascii')
|
||||
unquoted = urllib_unquote(uri)
|
||||
return unquoted.decode('utf-8')
|
||||
|
||||
# Old-style of re-raising an exception is SyntaxError in Python 3,
|
||||
# so hide behind exec() so the Python 3 parser doesn't see it
|
||||
exec('''def reraise(exc_type, exc_value, exc_traceback):
|
||||
"""Re-raise an exception given information from sys.exc_info()
|
||||
|
||||
Note that unlike six.reraise, this does not support replacing the
|
||||
traceback. All arguments must come from a single sys.exc_info() call.
|
||||
"""
|
||||
raise exc_type, exc_value, exc_traceback
|
||||
''')
|
||||
|
||||
else:
|
||||
from collections import UserDict
|
||||
IterableUserDict = UserDict
|
||||
from urllib.parse import quote, quote_plus, unquote, urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
def reraise(exc_type, exc_value, exc_traceback):
|
||||
"""Re-raise an exception given information from sys.exc_info()
|
||||
|
||||
Note that unlike six.reraise, this does not support replacing the
|
||||
traceback. All arguments must come from a single sys.exc_info() call.
|
||||
"""
|
||||
# In Python 3, all exception info is contained in one object.
|
||||
raise exc_value
|
||||
|
||||
try:
|
||||
from shutil import which
|
||||
except ImportError:
|
||||
# shutil.which() from Python 3.6
|
||||
# "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
# 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation;
|
||||
# All Rights Reserved"
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
path.
|
||||
|
||||
"""
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode)
|
||||
and not os.path.isdir(fn))
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
# current directory, e.g. ./script
|
||||
if os.path.dirname(cmd):
|
||||
if _access_check(cmd, mode):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
# The current directory takes precedence on Windows.
|
||||
if not os.curdir in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
# See if the given file matches any of the expected path extensions.
|
||||
# This will allow us to short circuit when given "python.exe".
|
||||
# If it does match, only test that one, otherwise we have to try
|
||||
# others.
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
files = [cmd]
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
# On other platforms you don't have things like PATHEXT to tell you
|
||||
# what file suffixes are executable, so just pass on cmd as-is.
|
||||
files = [cmd]
|
||||
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if not normdir in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
404
thesisenv/lib/python3.6/site-packages/ldap/constants.py
Normal file
404
thesisenv/lib/python3.6/site-packages/ldap/constants.py
Normal file
@ -0,0 +1,404 @@
|
||||
"""Definitions for constants exported by OpenLDAP
|
||||
|
||||
This file lists all constants we know about, even those that aren't
|
||||
available in the OpenLDAP version python-ldap is compiled against.
|
||||
|
||||
The information serves two purposes:
|
||||
|
||||
- Generate a C header with the constants
|
||||
- Provide support for building documentation without compiling python-ldap
|
||||
|
||||
"""
|
||||
|
||||
# This module cannot import anything from ldap.
|
||||
# When building documentation, it is used to initialize ldap.__init__.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
class Constant(object):
|
||||
"""Base class for a definition of an OpenLDAP constant
|
||||
"""
|
||||
|
||||
def __init__(self, name, optional=False, requirements=(), doc=None):
|
||||
self.name = name
|
||||
if optional:
|
||||
self_requirement = 'defined(LDAP_{})'.format(self.name)
|
||||
requirements = list(requirements) + [self_requirement]
|
||||
self.requirements = requirements
|
||||
self.doc = self.__doc__ = doc
|
||||
|
||||
|
||||
class Error(Constant):
|
||||
"""Definition for an OpenLDAP error code
|
||||
|
||||
This is a constant at the C level; in Python errors are provided as
|
||||
exception classes.
|
||||
"""
|
||||
|
||||
c_template = 'add_err({self.name});'
|
||||
|
||||
|
||||
class Int(Constant):
|
||||
"""Definition for an OpenLDAP integer constant"""
|
||||
|
||||
c_template = 'add_int({self.name});'
|
||||
|
||||
|
||||
class TLSInt(Int):
|
||||
"""Definition for a TLS integer constant -- requires HAVE_TLS"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
requrements = list(kwargs.get('requirements', ()))
|
||||
kwargs['requirements'] = ['HAVE_TLS'] + requrements
|
||||
super(TLSInt, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class Feature(Constant):
|
||||
"""Definition for a feature: 0 or 1 based on a C #ifdef
|
||||
|
||||
"""
|
||||
|
||||
c_template = '\n'.join([
|
||||
'',
|
||||
'#ifdef {self.c_feature}',
|
||||
'if (PyModule_AddIntConstant(m, "{self.name}", 1) != 0) return -1;',
|
||||
'#else',
|
||||
'if (PyModule_AddIntConstant(m, "{self.name}", 0) != 0) return -1;',
|
||||
'#endif',
|
||||
'',
|
||||
])
|
||||
|
||||
|
||||
def __init__(self, name, c_feature, **kwargs):
|
||||
super(Feature, self).__init__(name, **kwargs)
|
||||
self.c_feature = c_feature
|
||||
|
||||
|
||||
class Str(Constant):
|
||||
c_template = 'add_string({self.name});'
|
||||
|
||||
|
||||
API_2004 = 'LDAP_API_VERSION >= 2004'
|
||||
|
||||
CONSTANTS = (
|
||||
Error('ADMINLIMIT_EXCEEDED'),
|
||||
Error('AFFECTS_MULTIPLE_DSAS'),
|
||||
Error('ALIAS_DEREF_PROBLEM'),
|
||||
Error('ALIAS_PROBLEM'),
|
||||
Error('ALREADY_EXISTS'),
|
||||
Error('AUTH_METHOD_NOT_SUPPORTED'),
|
||||
Error('AUTH_UNKNOWN'),
|
||||
Error('BUSY'),
|
||||
Error('CLIENT_LOOP'),
|
||||
Error('COMPARE_FALSE'),
|
||||
Error('COMPARE_TRUE'),
|
||||
Error('CONFIDENTIALITY_REQUIRED'),
|
||||
Error('CONNECT_ERROR'),
|
||||
Error('CONSTRAINT_VIOLATION'),
|
||||
Error('CONTROL_NOT_FOUND'),
|
||||
Error('DECODING_ERROR'),
|
||||
Error('ENCODING_ERROR'),
|
||||
Error('FILTER_ERROR'),
|
||||
Error('INAPPROPRIATE_AUTH'),
|
||||
Error('INAPPROPRIATE_MATCHING'),
|
||||
Error('INSUFFICIENT_ACCESS'),
|
||||
Error('INVALID_CREDENTIALS'),
|
||||
Error('INVALID_DN_SYNTAX'),
|
||||
Error('INVALID_SYNTAX'),
|
||||
Error('IS_LEAF'),
|
||||
Error('LOCAL_ERROR'),
|
||||
Error('LOOP_DETECT'),
|
||||
Error('MORE_RESULTS_TO_RETURN'),
|
||||
Error('NAMING_VIOLATION'),
|
||||
Error('NO_MEMORY'),
|
||||
Error('NO_OBJECT_CLASS_MODS'),
|
||||
Error('NO_OBJECT_CLASS_MODS'),
|
||||
Error('NO_RESULTS_RETURNED'),
|
||||
Error('NO_SUCH_ATTRIBUTE'),
|
||||
Error('NO_SUCH_OBJECT'),
|
||||
Error('NOT_ALLOWED_ON_NONLEAF'),
|
||||
Error('NOT_ALLOWED_ON_RDN'),
|
||||
Error('NOT_SUPPORTED'),
|
||||
Error('OBJECT_CLASS_VIOLATION'),
|
||||
Error('OPERATIONS_ERROR'),
|
||||
Error('OTHER'),
|
||||
Error('PARAM_ERROR'),
|
||||
Error('PARTIAL_RESULTS'),
|
||||
Error('PROTOCOL_ERROR'),
|
||||
Error('REFERRAL'),
|
||||
Error('REFERRAL_LIMIT_EXCEEDED'),
|
||||
Error('RESULTS_TOO_LARGE'),
|
||||
Error('SASL_BIND_IN_PROGRESS'),
|
||||
Error('SERVER_DOWN'),
|
||||
Error('SIZELIMIT_EXCEEDED'),
|
||||
Error('STRONG_AUTH_NOT_SUPPORTED'),
|
||||
Error('STRONG_AUTH_REQUIRED'),
|
||||
Error('SUCCESS'),
|
||||
Error('TIMELIMIT_EXCEEDED'),
|
||||
Error('TIMEOUT'),
|
||||
Error('TYPE_OR_VALUE_EXISTS'),
|
||||
Error('UNAVAILABLE'),
|
||||
Error('UNAVAILABLE_CRITICAL_EXTENSION'),
|
||||
Error('UNDEFINED_TYPE'),
|
||||
Error('UNWILLING_TO_PERFORM'),
|
||||
Error('USER_CANCELLED'),
|
||||
Error('VLV_ERROR'),
|
||||
Error('X_PROXY_AUTHZ_FAILURE'),
|
||||
|
||||
Error('CANCELLED', requirements=['defined(LDAP_API_FEATURE_CANCEL)']),
|
||||
Error('NO_SUCH_OPERATION', requirements=['defined(LDAP_API_FEATURE_CANCEL)']),
|
||||
Error('TOO_LATE', requirements=['defined(LDAP_API_FEATURE_CANCEL)']),
|
||||
Error('CANNOT_CANCEL', requirements=['defined(LDAP_API_FEATURE_CANCEL)']),
|
||||
|
||||
Error('ASSERTION_FAILED', optional=True),
|
||||
|
||||
Error('PROXIED_AUTHORIZATION_DENIED', optional=True),
|
||||
|
||||
# simple constants
|
||||
|
||||
Int('API_VERSION'),
|
||||
Int('VENDOR_VERSION'),
|
||||
|
||||
Int('PORT'),
|
||||
Int('VERSION1'),
|
||||
Int('VERSION2'),
|
||||
Int('VERSION3'),
|
||||
Int('VERSION_MIN'),
|
||||
Int('VERSION'),
|
||||
Int('VERSION_MAX'),
|
||||
Int('TAG_MESSAGE'),
|
||||
Int('TAG_MSGID'),
|
||||
|
||||
Int('REQ_BIND'),
|
||||
Int('REQ_UNBIND'),
|
||||
Int('REQ_SEARCH'),
|
||||
Int('REQ_MODIFY'),
|
||||
Int('REQ_ADD'),
|
||||
Int('REQ_DELETE'),
|
||||
Int('REQ_MODRDN'),
|
||||
Int('REQ_COMPARE'),
|
||||
Int('REQ_ABANDON'),
|
||||
|
||||
Int('TAG_LDAPDN'),
|
||||
Int('TAG_LDAPCRED'),
|
||||
Int('TAG_CONTROLS'),
|
||||
Int('TAG_REFERRAL'),
|
||||
|
||||
Int('REQ_EXTENDED'),
|
||||
Int('TAG_NEWSUPERIOR', requirements=[API_2004]),
|
||||
Int('TAG_EXOP_REQ_OID', requirements=[API_2004]),
|
||||
Int('TAG_EXOP_REQ_VALUE', requirements=[API_2004]),
|
||||
Int('TAG_EXOP_RES_OID', requirements=[API_2004]),
|
||||
Int('TAG_EXOP_RES_VALUE', requirements=[API_2004]),
|
||||
Int('TAG_SASL_RES_CREDS', requirements=[API_2004, 'defined(HAVE_SASL)']),
|
||||
|
||||
Int('SASL_AUTOMATIC'),
|
||||
Int('SASL_INTERACTIVE'),
|
||||
Int('SASL_QUIET'),
|
||||
|
||||
# reversibles
|
||||
|
||||
Int('RES_BIND'),
|
||||
Int('RES_SEARCH_ENTRY'),
|
||||
Int('RES_SEARCH_RESULT'),
|
||||
Int('RES_MODIFY'),
|
||||
Int('RES_ADD'),
|
||||
Int('RES_DELETE'),
|
||||
Int('RES_MODRDN'),
|
||||
Int('RES_COMPARE'),
|
||||
Int('RES_ANY'),
|
||||
|
||||
Int('RES_SEARCH_REFERENCE'),
|
||||
Int('RES_EXTENDED'),
|
||||
Int('RES_UNSOLICITED'),
|
||||
|
||||
Int('RES_INTERMEDIATE'),
|
||||
|
||||
# non-reversibles
|
||||
|
||||
Int('AUTH_NONE'),
|
||||
Int('AUTH_SIMPLE'),
|
||||
Int('SCOPE_BASE'),
|
||||
Int('SCOPE_ONELEVEL'),
|
||||
Int('SCOPE_SUBTREE'),
|
||||
Int('SCOPE_SUBORDINATE', optional=True),
|
||||
Int('MOD_ADD'),
|
||||
Int('MOD_DELETE'),
|
||||
Int('MOD_REPLACE'),
|
||||
Int('MOD_INCREMENT'),
|
||||
Int('MOD_BVALUES'),
|
||||
|
||||
Int('MSG_ONE'),
|
||||
Int('MSG_ALL'),
|
||||
Int('MSG_RECEIVED'),
|
||||
|
||||
# (error constants handled above)
|
||||
|
||||
Int('DEREF_NEVER'),
|
||||
Int('DEREF_SEARCHING'),
|
||||
Int('DEREF_FINDING'),
|
||||
Int('DEREF_ALWAYS'),
|
||||
Int('NO_LIMIT'),
|
||||
|
||||
Int('OPT_API_INFO'),
|
||||
Int('OPT_DEREF'),
|
||||
Int('OPT_SIZELIMIT'),
|
||||
Int('OPT_TIMELIMIT'),
|
||||
Int('OPT_REFERRALS', optional=True),
|
||||
Int('OPT_ERROR_NUMBER'),
|
||||
Int('OPT_RESTART'),
|
||||
Int('OPT_PROTOCOL_VERSION'),
|
||||
Int('OPT_SERVER_CONTROLS'),
|
||||
Int('OPT_CLIENT_CONTROLS'),
|
||||
Int('OPT_API_FEATURE_INFO'),
|
||||
Int('OPT_HOST_NAME'),
|
||||
|
||||
Int('OPT_DESC'),
|
||||
Int('OPT_DIAGNOSTIC_MESSAGE'),
|
||||
|
||||
Int('OPT_ERROR_STRING'),
|
||||
Int('OPT_MATCHED_DN'),
|
||||
Int('OPT_DEBUG_LEVEL'),
|
||||
Int('OPT_TIMEOUT'),
|
||||
Int('OPT_REFHOPLIMIT'),
|
||||
Int('OPT_NETWORK_TIMEOUT'),
|
||||
Int('OPT_URI'),
|
||||
|
||||
Int('OPT_DEFBASE', optional=True),
|
||||
|
||||
TLSInt('OPT_X_TLS', optional=True),
|
||||
TLSInt('OPT_X_TLS_CTX'),
|
||||
TLSInt('OPT_X_TLS_CACERTFILE'),
|
||||
TLSInt('OPT_X_TLS_CACERTDIR'),
|
||||
TLSInt('OPT_X_TLS_CERTFILE'),
|
||||
TLSInt('OPT_X_TLS_KEYFILE'),
|
||||
TLSInt('OPT_X_TLS_REQUIRE_CERT'),
|
||||
TLSInt('OPT_X_TLS_CIPHER_SUITE'),
|
||||
TLSInt('OPT_X_TLS_RANDOM_FILE'),
|
||||
TLSInt('OPT_X_TLS_DHFILE'),
|
||||
TLSInt('OPT_X_TLS_NEVER'),
|
||||
TLSInt('OPT_X_TLS_HARD'),
|
||||
TLSInt('OPT_X_TLS_DEMAND'),
|
||||
TLSInt('OPT_X_TLS_ALLOW'),
|
||||
TLSInt('OPT_X_TLS_TRY'),
|
||||
TLSInt('OPT_X_TLS_PEERCERT', optional=True),
|
||||
|
||||
TLSInt('OPT_X_TLS_VERSION', optional=True),
|
||||
TLSInt('OPT_X_TLS_CIPHER', optional=True),
|
||||
TLSInt('OPT_X_TLS_PEERCERT', optional=True),
|
||||
|
||||
# only available if OpenSSL supports it => might cause
|
||||
# backward compatibility problems
|
||||
TLSInt('OPT_X_TLS_CRLCHECK', optional=True),
|
||||
|
||||
TLSInt('OPT_X_TLS_CRLFILE', optional=True),
|
||||
|
||||
TLSInt('OPT_X_TLS_CRL_NONE'),
|
||||
TLSInt('OPT_X_TLS_CRL_PEER'),
|
||||
TLSInt('OPT_X_TLS_CRL_ALL'),
|
||||
TLSInt('OPT_X_TLS_NEWCTX', optional=True),
|
||||
TLSInt('OPT_X_TLS_PROTOCOL_MIN', optional=True),
|
||||
TLSInt('OPT_X_TLS_PACKAGE', optional=True),
|
||||
|
||||
Int('OPT_X_SASL_MECH'),
|
||||
Int('OPT_X_SASL_REALM'),
|
||||
Int('OPT_X_SASL_AUTHCID'),
|
||||
Int('OPT_X_SASL_AUTHZID'),
|
||||
Int('OPT_X_SASL_SSF'),
|
||||
Int('OPT_X_SASL_SSF_EXTERNAL'),
|
||||
Int('OPT_X_SASL_SECPROPS'),
|
||||
Int('OPT_X_SASL_SSF_MIN'),
|
||||
Int('OPT_X_SASL_SSF_MAX'),
|
||||
Int('OPT_X_SASL_NOCANON', optional=True),
|
||||
Int('OPT_X_SASL_USERNAME', optional=True),
|
||||
Int('OPT_CONNECT_ASYNC', optional=True),
|
||||
Int('OPT_X_KEEPALIVE_IDLE', optional=True),
|
||||
Int('OPT_X_KEEPALIVE_PROBES', optional=True),
|
||||
Int('OPT_X_KEEPALIVE_INTERVAL', optional=True),
|
||||
|
||||
Int('DN_FORMAT_LDAP'),
|
||||
Int('DN_FORMAT_LDAPV3'),
|
||||
Int('DN_FORMAT_LDAPV2'),
|
||||
Int('DN_FORMAT_DCE'),
|
||||
Int('DN_FORMAT_UFN'),
|
||||
Int('DN_FORMAT_AD_CANONICAL'),
|
||||
# Int('DN_FORMAT_LBER'), # for testing only
|
||||
Int('DN_FORMAT_MASK'),
|
||||
Int('DN_PRETTY'),
|
||||
Int('DN_SKIP'),
|
||||
Int('DN_P_NOLEADTRAILSPACES'),
|
||||
Int('DN_P_NOSPACEAFTERRDN'),
|
||||
Int('DN_PEDANTIC'),
|
||||
|
||||
Int('AVA_NULL'),
|
||||
Int('AVA_STRING'),
|
||||
Int('AVA_BINARY'),
|
||||
Int('AVA_NONPRINTABLE'),
|
||||
|
||||
Int('OPT_SUCCESS'),
|
||||
|
||||
# XXX - these should be errors
|
||||
Int('URL_ERR_BADSCOPE'),
|
||||
Int('URL_ERR_MEM'),
|
||||
# Int('LIBLDAP_R'),
|
||||
|
||||
Feature('LIBLDAP_R', 'HAVE_LIBLDAP_R'),
|
||||
Feature('SASL_AVAIL', 'HAVE_SASL'),
|
||||
Feature('TLS_AVAIL', 'HAVE_TLS'),
|
||||
|
||||
Str("CONTROL_MANAGEDSAIT"),
|
||||
Str("CONTROL_PROXY_AUTHZ"),
|
||||
Str("CONTROL_SUBENTRIES"),
|
||||
Str("CONTROL_VALUESRETURNFILTER"),
|
||||
Str("CONTROL_ASSERT"),
|
||||
Str("CONTROL_PRE_READ"),
|
||||
Str("CONTROL_POST_READ"),
|
||||
Str("CONTROL_SORTREQUEST"),
|
||||
Str("CONTROL_SORTRESPONSE"),
|
||||
Str("CONTROL_PAGEDRESULTS"),
|
||||
Str("CONTROL_SYNC"),
|
||||
Str("CONTROL_SYNC_STATE"),
|
||||
Str("CONTROL_SYNC_DONE"),
|
||||
Str("SYNC_INFO"),
|
||||
Str("CONTROL_PASSWORDPOLICYREQUEST"),
|
||||
Str("CONTROL_PASSWORDPOLICYRESPONSE"),
|
||||
Str("CONTROL_RELAX"),
|
||||
)
|
||||
|
||||
|
||||
def print_header(): # pragma: no cover
|
||||
"""Print the C header file to standard output"""
|
||||
|
||||
print('/*')
|
||||
print(' * Generated with:')
|
||||
print(' * python Lib/ldap/constants.py > Modules/constants_generated.h')
|
||||
print(' *')
|
||||
print(' * Please do any modifications there, then re-generate this file')
|
||||
print(' */')
|
||||
print('')
|
||||
|
||||
current_requirements = []
|
||||
|
||||
def pop_requirement():
|
||||
popped = current_requirements.pop()
|
||||
print('#endif')
|
||||
print()
|
||||
|
||||
for definition in CONSTANTS:
|
||||
while not set(current_requirements).issubset(definition.requirements):
|
||||
pop_requirement()
|
||||
|
||||
for requirement in definition.requirements:
|
||||
if requirement not in current_requirements:
|
||||
current_requirements.append(requirement)
|
||||
print()
|
||||
print('#if {}'.format(requirement))
|
||||
|
||||
print(definition.c_template.format(self=definition))
|
||||
|
||||
while current_requirements:
|
||||
pop_requirement()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print_header()
|
158
thesisenv/lib/python3.6/site-packages/ldap/controls/__init__.py
Normal file
158
thesisenv/lib/python3.6/site-packages/ldap/controls/__init__.py
Normal file
@ -0,0 +1,158 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
controls.py - support classes for LDAP controls
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
|
||||
Description:
|
||||
The ldap.controls module provides LDAPControl classes.
|
||||
Each class provides support for a certain control.
|
||||
"""
|
||||
|
||||
from ldap.pkginfo import __version__
|
||||
|
||||
import _ldap
|
||||
assert _ldap.__version__==__version__, \
|
||||
ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__))
|
||||
|
||||
import ldap
|
||||
|
||||
from pyasn1.error import PyAsn1Error
|
||||
|
||||
|
||||
__all__ = [
|
||||
'KNOWN_RESPONSE_CONTROLS',
|
||||
# Classes
|
||||
'AssertionControl',
|
||||
'BooleanControl',
|
||||
'LDAPControl',
|
||||
'ManageDSAITControl',
|
||||
'MatchedValuesControl',
|
||||
'RelaxRulesControl',
|
||||
'RequestControl',
|
||||
'ResponseControl',
|
||||
'SimplePagedResultsControl',
|
||||
'ValueLessRequestControl',
|
||||
# Functions
|
||||
'RequestControlTuples',
|
||||
'DecodeControlTuples',
|
||||
]
|
||||
|
||||
# response control OID to class registry
|
||||
KNOWN_RESPONSE_CONTROLS = {}
|
||||
|
||||
|
||||
class RequestControl:
|
||||
"""
|
||||
Base class for all request controls
|
||||
|
||||
controlType
|
||||
OID as string of the LDAPv3 extended request control
|
||||
criticality
|
||||
sets the criticality of the control (boolean)
|
||||
encodedControlValue
|
||||
control value of the LDAPv3 extended request control
|
||||
(here it is the BER-encoded ASN.1 control value)
|
||||
"""
|
||||
|
||||
def __init__(self,controlType=None,criticality=False,encodedControlValue=None):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
self.encodedControlValue = encodedControlValue
|
||||
|
||||
def encodeControlValue(self):
|
||||
"""
|
||||
sets class attribute encodedControlValue to the BER-encoded ASN.1
|
||||
control value composed by class attributes set before
|
||||
"""
|
||||
return self.encodedControlValue
|
||||
|
||||
|
||||
class ResponseControl:
|
||||
"""
|
||||
Base class for all response controls
|
||||
|
||||
controlType
|
||||
OID as string of the LDAPv3 extended response control
|
||||
criticality
|
||||
sets the criticality of the received control (boolean)
|
||||
"""
|
||||
|
||||
def __init__(self,controlType=None,criticality=False):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
"""
|
||||
decodes the BER-encoded ASN.1 control value and sets the appropriate
|
||||
class attributes
|
||||
"""
|
||||
self.encodedControlValue = encodedControlValue
|
||||
|
||||
|
||||
class LDAPControl(RequestControl,ResponseControl):
|
||||
"""
|
||||
Base class for combined request/response controls mainly
|
||||
for backward-compatibility to python-ldap 2.3.x
|
||||
"""
|
||||
|
||||
def __init__(self,controlType=None,criticality=False,controlValue=None,encodedControlValue=None):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
self.controlValue = controlValue
|
||||
self.encodedControlValue = encodedControlValue
|
||||
|
||||
|
||||
def RequestControlTuples(ldapControls):
|
||||
"""
|
||||
Return list of readily encoded 3-tuples which can be directly
|
||||
passed to C module _ldap
|
||||
|
||||
ldapControls
|
||||
sequence-type of RequestControl objects
|
||||
"""
|
||||
if ldapControls is None:
|
||||
return None
|
||||
else:
|
||||
result = [
|
||||
(c.controlType,c.criticality,c.encodeControlValue())
|
||||
for c in ldapControls
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
def DecodeControlTuples(ldapControlTuples,knownLDAPControls=None):
|
||||
"""
|
||||
Returns list of readily decoded ResponseControl objects
|
||||
|
||||
ldapControlTuples
|
||||
Sequence-type of 3-tuples returned by _ldap.result4() containing
|
||||
the encoded ASN.1 control values of response controls.
|
||||
knownLDAPControls
|
||||
Dictionary mapping extended control's OID to ResponseControl class
|
||||
of response controls known by the application. If None
|
||||
ldap.controls.KNOWN_RESPONSE_CONTROLS is used here.
|
||||
"""
|
||||
knownLDAPControls = knownLDAPControls or KNOWN_RESPONSE_CONTROLS
|
||||
result = []
|
||||
for controlType,criticality,encodedControlValue in ldapControlTuples or []:
|
||||
try:
|
||||
control = knownLDAPControls[controlType]()
|
||||
except KeyError:
|
||||
if criticality:
|
||||
raise ldap.UNAVAILABLE_CRITICAL_EXTENSION('Received unexpected critical response control with controlType %s' % (repr(controlType)))
|
||||
else:
|
||||
control.controlType,control.criticality = controlType,criticality
|
||||
try:
|
||||
control.decodeControlValue(encodedControlValue)
|
||||
except PyAsn1Error:
|
||||
if criticality:
|
||||
raise
|
||||
else:
|
||||
result.append(control)
|
||||
return result
|
||||
|
||||
|
||||
# Import the standard sub-modules
|
||||
from ldap.controls.simple import *
|
||||
from ldap.controls.libldap import *
|
119
thesisenv/lib/python3.6/site-packages/ldap/controls/deref.py
Normal file
119
thesisenv/lib/python3.6/site-packages/ldap/controls/deref.py
Normal file
@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.deref - classes for
|
||||
(see https://tools.ietf.org/html/draft-masarati-ldap-deref)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'DEREF_CONTROL_OID',
|
||||
'DereferenceControl',
|
||||
]
|
||||
|
||||
import ldap.controls
|
||||
from ldap.controls import LDAPControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
import pyasn1_modules.rfc2251
|
||||
from pyasn1.type import namedtype,univ,tag
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
from pyasn1_modules.rfc2251 import LDAPDN,AttributeDescription,AttributeDescriptionList,AttributeValue
|
||||
|
||||
|
||||
DEREF_CONTROL_OID = '1.3.6.1.4.1.4203.666.5.16'
|
||||
|
||||
|
||||
# Request types
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# For compatibility with ASN.1 declaration in I-D
|
||||
AttributeList = AttributeDescriptionList
|
||||
|
||||
class DerefSpec(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'derefAttr',
|
||||
AttributeDescription()
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'attributes',
|
||||
AttributeList()
|
||||
),
|
||||
)
|
||||
|
||||
class DerefSpecs(univ.SequenceOf):
|
||||
componentType = DerefSpec()
|
||||
|
||||
# Response types
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class AttributeValues(univ.SetOf):
|
||||
componentType = AttributeValue()
|
||||
|
||||
|
||||
class PartialAttribute(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('type', AttributeDescription()),
|
||||
namedtype.NamedType('vals', AttributeValues()),
|
||||
)
|
||||
|
||||
|
||||
class PartialAttributeList(univ.SequenceOf):
|
||||
componentType = PartialAttribute()
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassContext,tag.tagFormatConstructed,0)
|
||||
)
|
||||
|
||||
|
||||
class DerefRes(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('derefAttr', AttributeDescription()),
|
||||
namedtype.NamedType('derefVal', LDAPDN()),
|
||||
namedtype.OptionalNamedType('attrVals', PartialAttributeList()),
|
||||
)
|
||||
|
||||
|
||||
class DerefResultControlValue(univ.SequenceOf):
|
||||
componentType = DerefRes()
|
||||
|
||||
|
||||
class DereferenceControl(LDAPControl):
|
||||
controlType = DEREF_CONTROL_OID
|
||||
|
||||
def __init__(self,criticality=False,derefSpecs=None):
|
||||
LDAPControl.__init__(self,self.controlType,criticality)
|
||||
self.derefSpecs = derefSpecs or {}
|
||||
|
||||
def _derefSpecs(self):
|
||||
deref_specs = DerefSpecs()
|
||||
i = 0
|
||||
for deref_attr,deref_attribute_names in self.derefSpecs.items():
|
||||
deref_spec = DerefSpec()
|
||||
deref_attributes = AttributeList()
|
||||
for j in range(len(deref_attribute_names)):
|
||||
deref_attributes.setComponentByPosition(j,deref_attribute_names[j])
|
||||
deref_spec.setComponentByName('derefAttr',AttributeDescription(deref_attr))
|
||||
deref_spec.setComponentByName('attributes',deref_attributes)
|
||||
deref_specs.setComponentByPosition(i,deref_spec)
|
||||
i += 1
|
||||
return deref_specs
|
||||
|
||||
def encodeControlValue(self):
|
||||
return encoder.encode(self._derefSpecs())
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=DerefResultControlValue())
|
||||
self.derefRes = {}
|
||||
for deref_res in decodedValue:
|
||||
deref_attr,deref_val,deref_vals = deref_res[0],deref_res[1],deref_res[2]
|
||||
partial_attrs_dict = {
|
||||
str(tv[0]): [str(v) for v in tv[1]]
|
||||
for tv in deref_vals or []
|
||||
}
|
||||
try:
|
||||
self.derefRes[str(deref_attr)].append((str(deref_val),partial_attrs_dict))
|
||||
except KeyError:
|
||||
self.derefRes[str(deref_attr)] = [(str(deref_val),partial_attrs_dict)]
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[DereferenceControl.controlType] = DereferenceControl
|
@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
controls.libldap - LDAP controls wrapper classes with en-/decoding done
|
||||
by OpenLDAP functions
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap.pkginfo import __version__
|
||||
|
||||
import _ldap
|
||||
assert _ldap.__version__==__version__, \
|
||||
ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__))
|
||||
|
||||
import ldap
|
||||
|
||||
from ldap.controls import RequestControl,LDAPControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
|
||||
class AssertionControl(RequestControl):
|
||||
"""
|
||||
LDAP Assertion control, as defined in RFC 4528
|
||||
|
||||
filterstr
|
||||
LDAP filter string specifying which assertions have to match
|
||||
so that the server processes the operation
|
||||
"""
|
||||
|
||||
controlType = ldap.CONTROL_ASSERT
|
||||
def __init__(self,criticality=True,filterstr='(objectClass=*)'):
|
||||
self.criticality = criticality
|
||||
self.filterstr = filterstr
|
||||
|
||||
def encodeControlValue(self):
|
||||
return _ldap.encode_assertion_control(self.filterstr)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_ASSERT] = AssertionControl
|
||||
|
||||
|
||||
class MatchedValuesControl(RequestControl):
|
||||
"""
|
||||
LDAP Matched Values control, as defined in RFC 3876
|
||||
|
||||
filterstr
|
||||
LDAP filter string specifying which attribute values
|
||||
should be returned
|
||||
"""
|
||||
|
||||
controlType = ldap.CONTROL_VALUESRETURNFILTER
|
||||
|
||||
def __init__(self,criticality=False,filterstr='(objectClass=*)'):
|
||||
self.criticality = criticality
|
||||
self.filterstr = filterstr
|
||||
|
||||
def encodeControlValue(self):
|
||||
return _ldap.encode_valuesreturnfilter_control(self.filterstr)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_VALUESRETURNFILTER] = MatchedValuesControl
|
||||
|
||||
|
||||
class SimplePagedResultsControl(LDAPControl):
|
||||
"""
|
||||
LDAP Control Extension for Simple Paged Results Manipulation
|
||||
|
||||
size
|
||||
Page size requested (number of entries to be returned)
|
||||
cookie
|
||||
Cookie string received with last page
|
||||
"""
|
||||
controlType = ldap.CONTROL_PAGEDRESULTS
|
||||
|
||||
def __init__(self,criticality=False,size=None,cookie=None):
|
||||
self.criticality = criticality
|
||||
self.size,self.cookie = size,cookie
|
||||
|
||||
def encodeControlValue(self):
|
||||
return _ldap.encode_page_control(self.size,self.cookie)
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.size,self.cookie = _ldap.decode_page_control(encodedControlValue)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_PAGEDRESULTS] = SimplePagedResultsControl
|
@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.openldap - classes for OpenLDAP-specific controls
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
import ldap.controls
|
||||
from ldap.controls import ValueLessRequestControl,ResponseControl
|
||||
|
||||
from pyasn1.type import univ
|
||||
from pyasn1.codec.ber import decoder
|
||||
|
||||
|
||||
__all__ = [
|
||||
'SearchNoOpControl',
|
||||
'SearchNoOpMixIn',
|
||||
]
|
||||
|
||||
|
||||
class SearchNoOpControl(ValueLessRequestControl,ResponseControl):
|
||||
"""
|
||||
No-op control attached to search operations implementing sort of a
|
||||
count operation
|
||||
|
||||
see https://www.openldap.org/its/index.cgi?findid=6598
|
||||
"""
|
||||
controlType = '1.3.6.1.4.1.4203.666.5.18'
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
self.criticality = criticality
|
||||
|
||||
class SearchNoOpControlValue(univ.Sequence):
|
||||
pass
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=self.SearchNoOpControlValue())
|
||||
self.resultCode = int(decodedValue[0])
|
||||
self.numSearchResults = int(decodedValue[1])
|
||||
self.numSearchContinuations = int(decodedValue[2])
|
||||
|
||||
|
||||
ldap.controls.KNOWN_RESPONSE_CONTROLS[SearchNoOpControl.controlType] = SearchNoOpControl
|
||||
|
||||
|
||||
class SearchNoOpMixIn:
|
||||
"""
|
||||
Mix-in class to be used with class LDAPObject and friends.
|
||||
|
||||
It adds a convenience method noop_search_st() to LDAPObject
|
||||
for easily using the no-op search control.
|
||||
"""
|
||||
|
||||
def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*)',timeout=-1):
|
||||
try:
|
||||
msg_id = self.search_ext(
|
||||
base,
|
||||
scope,
|
||||
filterstr=filterstr,
|
||||
attrlist=['1.1'],
|
||||
timeout=timeout,
|
||||
serverctrls=[SearchNoOpControl(criticality=True)],
|
||||
)
|
||||
_,_,_,search_response_ctrls = self.result3(msg_id,all=1,timeout=timeout)
|
||||
except (
|
||||
ldap.TIMEOUT,
|
||||
ldap.TIMELIMIT_EXCEEDED,
|
||||
ldap.SIZELIMIT_EXCEEDED,
|
||||
ldap.ADMINLIMIT_EXCEEDED
|
||||
) as e:
|
||||
self.abandon(msg_id)
|
||||
raise e
|
||||
else:
|
||||
noop_srch_ctrl = [
|
||||
c
|
||||
for c in search_response_ctrls
|
||||
if c.controlType==SearchNoOpControl.controlType
|
||||
]
|
||||
if noop_srch_ctrl:
|
||||
return noop_srch_ctrl[0].numSearchResults,noop_srch_ctrl[0].numSearchContinuations
|
||||
else:
|
||||
return (None,None)
|
@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.paged - classes for Simple Paged control
|
||||
(see RFC 2696)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'SimplePagedResultsControl'
|
||||
]
|
||||
|
||||
# Imports from python-ldap 2.4+
|
||||
import ldap.controls
|
||||
from ldap.controls import RequestControl,ResponseControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import tag,namedtype,univ,constraint
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
from pyasn1_modules.rfc2251 import LDAPString
|
||||
|
||||
|
||||
class PagedResultsControlValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('size',univ.Integer()),
|
||||
namedtype.NamedType('cookie',LDAPString()),
|
||||
)
|
||||
|
||||
|
||||
class SimplePagedResultsControl(RequestControl,ResponseControl):
|
||||
controlType = '1.2.840.113556.1.4.319'
|
||||
|
||||
def __init__(self,criticality=False,size=10,cookie=''):
|
||||
self.criticality = criticality
|
||||
self.size = size
|
||||
self.cookie = cookie or ''
|
||||
|
||||
def encodeControlValue(self):
|
||||
pc = PagedResultsControlValue()
|
||||
pc.setComponentByName('size',univ.Integer(self.size))
|
||||
pc.setComponentByName('cookie',LDAPString(self.cookie))
|
||||
return encoder.encode(pc)
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=PagedResultsControlValue())
|
||||
self.size = int(decodedValue.getComponentByName('size'))
|
||||
self.cookie = bytes(decodedValue.getComponentByName('cookie'))
|
||||
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SimplePagedResultsControl.controlType] = SimplePagedResultsControl
|
@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.ppolicy - classes for Password Policy controls
|
||||
(see https://tools.ietf.org/html/draft-behera-ldap-password-policy)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'PasswordPolicyControl'
|
||||
]
|
||||
|
||||
# Imports from python-ldap 2.4+
|
||||
from ldap.controls import (
|
||||
ResponseControl, ValueLessRequestControl, KNOWN_RESPONSE_CONTROLS
|
||||
)
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import tag,namedtype,namedval,univ,constraint
|
||||
from pyasn1.codec.der import decoder
|
||||
|
||||
|
||||
class PasswordPolicyWarning(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('timeBeforeExpiration',univ.Integer().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,0)
|
||||
)),
|
||||
namedtype.NamedType('graceAuthNsRemaining',univ.Integer().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
|
||||
)),
|
||||
)
|
||||
|
||||
|
||||
class PasswordPolicyError(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
('passwordExpired',0),
|
||||
('accountLocked',1),
|
||||
('changeAfterReset',2),
|
||||
('passwordModNotAllowed',3),
|
||||
('mustSupplyOldPassword',4),
|
||||
('insufficientPasswordQuality',5),
|
||||
('passwordTooShort',6),
|
||||
('passwordTooYoung',7),
|
||||
('passwordInHistory',8)
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8)
|
||||
|
||||
|
||||
class PasswordPolicyResponseValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType(
|
||||
'warning',
|
||||
PasswordPolicyWarning().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,0)
|
||||
),
|
||||
),
|
||||
namedtype.OptionalNamedType(
|
||||
'error',PasswordPolicyError().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class PasswordPolicyControl(ValueLessRequestControl,ResponseControl):
|
||||
controlType = '1.3.6.1.4.1.42.2.27.8.5.1'
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
self.criticality = criticality
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue())
|
||||
self.timeBeforeExpiration = None
|
||||
self.graceAuthNsRemaining = None
|
||||
self.error = None
|
||||
|
||||
warning = ppolicyValue.getComponentByName('warning')
|
||||
if warning.hasValue():
|
||||
if 'timeBeforeExpiration' in warning:
|
||||
self.timeBeforeExpiration = int(
|
||||
warning.getComponentByName('timeBeforeExpiration'))
|
||||
if 'graceAuthNsRemaining' in warning:
|
||||
self.graceAuthNsRemaining = int(
|
||||
warning.getComponentByName('graceAuthNsRemaining'))
|
||||
|
||||
error = ppolicyValue.getComponentByName('error')
|
||||
if error.hasValue():
|
||||
self.error = int(error)
|
||||
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[PasswordPolicyControl.controlType] = PasswordPolicyControl
|
130
thesisenv/lib/python3.6/site-packages/ldap/controls/psearch.py
Normal file
130
thesisenv/lib/python3.6/site-packages/ldap/controls/psearch.py
Normal file
@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.psearch - classes for Persistent Search Control
|
||||
(see https://tools.ietf.org/html/draft-ietf-ldapext-psearch)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'PersistentSearchControl',
|
||||
'EntryChangeNotificationControl',
|
||||
'CHANGE_TYPES_INT',
|
||||
'CHANGE_TYPES_STR',
|
||||
]
|
||||
|
||||
# Imports from python-ldap 2.4+
|
||||
import ldap.controls
|
||||
from ldap.controls import RequestControl,ResponseControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import namedtype,namedval,univ,constraint
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
from pyasn1_modules.rfc2251 import LDAPDN
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Constants and classes for Persistent Search Control
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
CHANGE_TYPES_INT = {
|
||||
'add':1,
|
||||
'delete':2,
|
||||
'modify':4,
|
||||
'modDN':8,
|
||||
}
|
||||
CHANGE_TYPES_STR = {v: k for k,v in CHANGE_TYPES_INT.items()}
|
||||
|
||||
|
||||
class PersistentSearchControl(RequestControl):
|
||||
"""
|
||||
Implements the request control for persistent search.
|
||||
|
||||
changeTypes
|
||||
List of strings specifying the types of changes returned by the server.
|
||||
Setting to None requests all changes.
|
||||
changesOnly
|
||||
Boolean which indicates whether only changes are returned by the server.
|
||||
returnECs
|
||||
Boolean which indicates whether the server should return an
|
||||
Entry Change Notification response control
|
||||
"""
|
||||
|
||||
class PersistentSearchControlValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('changeTypes',univ.Integer()),
|
||||
namedtype.NamedType('changesOnly',univ.Boolean()),
|
||||
namedtype.NamedType('returnECs',univ.Boolean()),
|
||||
)
|
||||
|
||||
controlType = "2.16.840.1.113730.3.4.3"
|
||||
|
||||
def __init__(self,criticality=True,changeTypes=None,changesOnly=False,returnECs=True):
|
||||
self.criticality,self.changesOnly,self.returnECs = \
|
||||
criticality,changesOnly,returnECs
|
||||
self.changeTypes = changeTypes or CHANGE_TYPES_INT.values()
|
||||
|
||||
def encodeControlValue(self):
|
||||
if not type(self.changeTypes)==type(0):
|
||||
# Assume a sequence type of integers to be OR-ed
|
||||
changeTypes_int = 0
|
||||
for ct in self.changeTypes:
|
||||
changeTypes_int = changeTypes_int|CHANGE_TYPES_INT.get(ct,ct)
|
||||
self.changeTypes = changeTypes_int
|
||||
p = self.PersistentSearchControlValue()
|
||||
p.setComponentByName('changeTypes',univ.Integer(self.changeTypes))
|
||||
p.setComponentByName('changesOnly',univ.Boolean(self.changesOnly))
|
||||
p.setComponentByName('returnECs',univ.Boolean(self.returnECs))
|
||||
return encoder.encode(p)
|
||||
|
||||
|
||||
class ChangeType(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
('add',1),
|
||||
('delete',2),
|
||||
('modify',4),
|
||||
('modDN',8),
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1,2,4,8)
|
||||
|
||||
|
||||
class EntryChangeNotificationValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('changeType',ChangeType()),
|
||||
namedtype.OptionalNamedType('previousDN', LDAPDN()),
|
||||
namedtype.OptionalNamedType('changeNumber',univ.Integer()),
|
||||
)
|
||||
|
||||
|
||||
class EntryChangeNotificationControl(ResponseControl):
|
||||
"""
|
||||
Implements the response control for persistent search.
|
||||
|
||||
Class attributes with values extracted from the response control:
|
||||
|
||||
changeType
|
||||
String indicating the type of change causing this result to be
|
||||
returned by the server
|
||||
previousDN
|
||||
Old DN of the entry in case of a modrdn change
|
||||
changeNumber
|
||||
A change serial number returned by the server (optional).
|
||||
"""
|
||||
|
||||
controlType = "2.16.840.1.113730.3.4.7"
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
ecncValue,_ = decoder.decode(encodedControlValue,asn1Spec=EntryChangeNotificationValue())
|
||||
self.changeType = int(ecncValue.getComponentByName('changeType'))
|
||||
previousDN = ecncValue.getComponentByName('previousDN')
|
||||
if previousDN.hasValue():
|
||||
self.previousDN = str(previousDN)
|
||||
else:
|
||||
self.previousDN = None
|
||||
changeNumber = ecncValue.getComponentByName('changeNumber')
|
||||
if changeNumber.hasValue():
|
||||
self.changeNumber = int(changeNumber)
|
||||
else:
|
||||
self.changeNumber = None
|
||||
return (self.changeType,self.previousDN,self.changeNumber)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[EntryChangeNotificationControl.controlType] = EntryChangeNotificationControl
|
@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.pwdpolicy - classes for Password Policy controls
|
||||
(see https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'PasswordExpiringControl',
|
||||
'PasswordExpiredControl',
|
||||
]
|
||||
|
||||
# Imports from python-ldap 2.4+
|
||||
import ldap.controls
|
||||
from ldap.controls import RequestControl,ResponseControl,ValueLessRequestControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
|
||||
class PasswordExpiringControl(ResponseControl):
|
||||
"""
|
||||
Indicates time in seconds when password will expire
|
||||
"""
|
||||
controlType = '2.16.840.1.113730.3.4.5'
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.gracePeriod = int(encodedControlValue)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[PasswordExpiringControl.controlType] = PasswordExpiringControl
|
||||
|
||||
|
||||
class PasswordExpiredControl(ResponseControl):
|
||||
"""
|
||||
Indicates that password is expired
|
||||
"""
|
||||
controlType = '2.16.840.1.113730.3.4.4'
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.passwordExpired = encodedControlValue=='0'
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[PasswordExpiredControl.controlType] = PasswordExpiredControl
|
@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.readentry - classes for the Read Entry controls
|
||||
(see RFC 4527)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
import ldap
|
||||
|
||||
from pyasn1.codec.ber import encoder,decoder
|
||||
from ldap.controls import LDAPControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
from pyasn1_modules.rfc2251 import AttributeDescriptionList,SearchResultEntry
|
||||
|
||||
|
||||
class ReadEntryControl(LDAPControl):
|
||||
"""
|
||||
Base class for read entry control described in RFC 4527
|
||||
|
||||
attrList
|
||||
list of attribute type names requested
|
||||
|
||||
Class attributes with values extracted from the response control:
|
||||
|
||||
dn
|
||||
string holding the distinguished name of the LDAP entry
|
||||
entry
|
||||
dictionary holding the LDAP entry
|
||||
"""
|
||||
|
||||
def __init__(self,criticality=False,attrList=None):
|
||||
self.criticality,self.attrList,self.entry = criticality,attrList or [],None
|
||||
|
||||
def encodeControlValue(self):
|
||||
attributeSelection = AttributeDescriptionList()
|
||||
for i in range(len(self.attrList)):
|
||||
attributeSelection.setComponentByPosition(i,self.attrList[i])
|
||||
return encoder.encode(attributeSelection)
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
decodedEntry,_ = decoder.decode(encodedControlValue,asn1Spec=SearchResultEntry())
|
||||
self.dn = str(decodedEntry[0])
|
||||
self.entry = {}
|
||||
for attr in decodedEntry[1]:
|
||||
self.entry[str(attr[0])] = [ str(attr_value) for attr_value in attr[1] ]
|
||||
|
||||
|
||||
class PreReadControl(ReadEntryControl):
|
||||
"""
|
||||
Class for pre-read control described in RFC 4527
|
||||
|
||||
attrList
|
||||
list of attribute type names requested
|
||||
|
||||
Class attributes with values extracted from the response control:
|
||||
|
||||
dn
|
||||
string holding the distinguished name of the LDAP entry
|
||||
before the operation was done by the server
|
||||
entry
|
||||
dictionary holding the LDAP entry
|
||||
before the operation was done by the server
|
||||
"""
|
||||
controlType = ldap.CONTROL_PRE_READ
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[PreReadControl.controlType] = PreReadControl
|
||||
|
||||
|
||||
class PostReadControl(ReadEntryControl):
|
||||
"""
|
||||
Class for post-read control described in RFC 4527
|
||||
|
||||
attrList
|
||||
list of attribute type names requested
|
||||
|
||||
Class attributes with values extracted from the response control:
|
||||
|
||||
dn
|
||||
string holding the distinguished name of the LDAP entry
|
||||
after the operation was done by the server
|
||||
entry
|
||||
dictionary holding the LDAP entry
|
||||
after the operation was done by the server
|
||||
"""
|
||||
controlType = ldap.CONTROL_POST_READ
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[PostReadControl.controlType] = PostReadControl
|
@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.sessiontrack - class for session tracking control
|
||||
(see draft-wahl-ldap-session)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
from ldap.controls import RequestControl
|
||||
|
||||
from pyasn1.type import namedtype,univ
|
||||
from pyasn1.codec.ber import encoder
|
||||
from pyasn1_modules.rfc2251 import LDAPString,LDAPOID
|
||||
|
||||
|
||||
# OID constants
|
||||
SESSION_TRACKING_CONTROL_OID = "1.3.6.1.4.1.21008.108.63.1"
|
||||
SESSION_TRACKING_FORMAT_OID_RADIUS_ACCT_SESSION_ID = SESSION_TRACKING_CONTROL_OID+".1"
|
||||
SESSION_TRACKING_FORMAT_OID_RADIUS_ACCT_MULTI_SESSION_ID = SESSION_TRACKING_CONTROL_OID+".2"
|
||||
SESSION_TRACKING_FORMAT_OID_USERNAME = SESSION_TRACKING_CONTROL_OID+".3"
|
||||
|
||||
|
||||
class SessionTrackingControl(RequestControl):
|
||||
"""
|
||||
Class for Session Tracking Control
|
||||
|
||||
Because criticality MUST be false for this control it cannot be set
|
||||
from the application.
|
||||
|
||||
sessionSourceIp
|
||||
IP address of the request source as string
|
||||
sessionSourceName
|
||||
Name of the request source as string
|
||||
formatOID
|
||||
OID as string specifying the format
|
||||
sessionTrackingIdentifier
|
||||
String containing a specific tracking ID
|
||||
"""
|
||||
|
||||
class SessionIdentifierControlValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('sessionSourceIp',LDAPString()),
|
||||
namedtype.NamedType('sessionSourceName',LDAPString()),
|
||||
namedtype.NamedType('formatOID',LDAPOID()),
|
||||
namedtype.NamedType('sessionTrackingIdentifier',LDAPString()),
|
||||
)
|
||||
|
||||
controlType = SESSION_TRACKING_CONTROL_OID
|
||||
|
||||
def __init__(self,sessionSourceIp,sessionSourceName,formatOID,sessionTrackingIdentifier):
|
||||
# criticality MUST be false for this control
|
||||
self.criticality = False
|
||||
self.sessionSourceIp,self.sessionSourceName,self.formatOID,self.sessionTrackingIdentifier = \
|
||||
sessionSourceIp,sessionSourceName,formatOID,sessionTrackingIdentifier
|
||||
|
||||
def encodeControlValue(self):
|
||||
s = self.SessionIdentifierControlValue()
|
||||
s.setComponentByName('sessionSourceIp',LDAPString(self.sessionSourceIp))
|
||||
s.setComponentByName('sessionSourceName',LDAPString(self.sessionSourceName))
|
||||
s.setComponentByName('formatOID',LDAPOID(self.formatOID))
|
||||
s.setComponentByName('sessionTrackingIdentifier',LDAPString(self.sessionTrackingIdentifier))
|
||||
return encoder.encode(s)
|
145
thesisenv/lib/python3.6/site-packages/ldap/controls/simple.py
Normal file
145
thesisenv/lib/python3.6/site-packages/ldap/controls/simple.py
Normal file
@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.simple - classes for some very simple LDAP controls
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import struct,ldap
|
||||
from ldap.controls import RequestControl,ResponseControl,LDAPControl,KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
|
||||
class ValueLessRequestControl(RequestControl):
|
||||
"""
|
||||
Base class for controls without a controlValue.
|
||||
The presence of the control in a LDAPv3 request changes the server's
|
||||
behaviour when processing the request simply based on the controlType.
|
||||
|
||||
controlType
|
||||
OID of the request control
|
||||
criticality
|
||||
criticality request control
|
||||
"""
|
||||
|
||||
def __init__(self,controlType=None,criticality=False):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
|
||||
def encodeControlValue(self):
|
||||
return None
|
||||
|
||||
|
||||
class OctetStringInteger(LDAPControl):
|
||||
"""
|
||||
Base class with controlValue being unsigend integer values
|
||||
|
||||
integerValue
|
||||
Integer to be sent as OctetString
|
||||
"""
|
||||
|
||||
def __init__(self,controlType=None,criticality=False,integerValue=None):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
self.integerValue = integerValue
|
||||
|
||||
def encodeControlValue(self):
|
||||
return struct.pack('!Q',self.integerValue)
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.integerValue = struct.unpack('!Q',encodedControlValue)[0]
|
||||
|
||||
|
||||
class BooleanControl(LDAPControl):
|
||||
"""
|
||||
Base class for simple request controls with boolean control value.
|
||||
|
||||
Constructor argument and class attribute:
|
||||
|
||||
booleanValue
|
||||
Boolean (True/False or 1/0) which is the boolean controlValue.
|
||||
"""
|
||||
boolean2ber = { 1:'\x01\x01\xFF', 0:'\x01\x01\x00' }
|
||||
ber2boolean = { '\x01\x01\xFF':1, '\x01\x01\x00':0 }
|
||||
|
||||
def __init__(self,controlType=None,criticality=False,booleanValue=False):
|
||||
self.controlType = controlType
|
||||
self.criticality = criticality
|
||||
self.booleanValue = booleanValue
|
||||
|
||||
def encodeControlValue(self):
|
||||
return self.boolean2ber[int(self.booleanValue)]
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.booleanValue = self.ber2boolean[encodedControlValue]
|
||||
|
||||
|
||||
class ManageDSAITControl(ValueLessRequestControl):
|
||||
"""
|
||||
Manage DSA IT Control
|
||||
"""
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
ValueLessRequestControl.__init__(self,ldap.CONTROL_MANAGEDSAIT,criticality=False)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_MANAGEDSAIT] = ManageDSAITControl
|
||||
|
||||
|
||||
class RelaxRulesControl(ValueLessRequestControl):
|
||||
"""
|
||||
Relax Rules Control
|
||||
"""
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
ValueLessRequestControl.__init__(self,ldap.CONTROL_RELAX,criticality=False)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[ldap.CONTROL_RELAX] = RelaxRulesControl
|
||||
|
||||
|
||||
class ProxyAuthzControl(RequestControl):
|
||||
"""
|
||||
Proxy Authorization Control
|
||||
|
||||
authzId
|
||||
string containing the authorization ID indicating the identity
|
||||
on behalf which the server should process the request
|
||||
"""
|
||||
|
||||
def __init__(self,criticality,authzId):
|
||||
RequestControl.__init__(self,ldap.CONTROL_PROXY_AUTHZ,criticality,authzId)
|
||||
|
||||
|
||||
class AuthorizationIdentityRequestControl(ValueLessRequestControl):
|
||||
"""
|
||||
Authorization Identity Request and Response Controls
|
||||
"""
|
||||
controlType = '2.16.840.1.113730.3.4.16'
|
||||
|
||||
def __init__(self,criticality):
|
||||
ValueLessRequestControl.__init__(self,self.controlType,criticality)
|
||||
|
||||
|
||||
class AuthorizationIdentityResponseControl(ResponseControl):
|
||||
"""
|
||||
Authorization Identity Request and Response Controls
|
||||
|
||||
Class attributes:
|
||||
|
||||
authzId
|
||||
decoded authorization identity
|
||||
"""
|
||||
controlType = '2.16.840.1.113730.3.4.15'
|
||||
|
||||
def decodeControlValue(self,encodedControlValue):
|
||||
self.authzId = encodedControlValue
|
||||
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[AuthorizationIdentityResponseControl.controlType] = AuthorizationIdentityResponseControl
|
||||
|
||||
|
||||
class GetEffectiveRightsControl(RequestControl):
|
||||
"""
|
||||
Get Effective Rights Control
|
||||
"""
|
||||
|
||||
def __init__(self,criticality,authzId=None):
|
||||
RequestControl.__init__(self,'1.3.6.1.4.1.42.2.27.9.5.2',criticality,authzId)
|
133
thesisenv/lib/python3.6/site-packages/ldap/controls/sss.py
Normal file
133
thesisenv/lib/python3.6/site-packages/ldap/controls/sss.py
Normal file
@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.sss - classes for Server Side Sorting
|
||||
(see RFC 2891)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'SSSRequestControl',
|
||||
'SSSResponseControl',
|
||||
]
|
||||
|
||||
|
||||
import ldap
|
||||
from ldap.ldapobject import LDAPObject
|
||||
from ldap.controls import (RequestControl, ResponseControl,
|
||||
KNOWN_RESPONSE_CONTROLS, DecodeControlTuples)
|
||||
|
||||
from pyasn1.type import univ, namedtype, tag, namedval, constraint
|
||||
from pyasn1.codec.ber import encoder, decoder
|
||||
|
||||
|
||||
# SortKeyList ::= SEQUENCE OF SEQUENCE {
|
||||
# attributeType AttributeDescription,
|
||||
# orderingRule [0] MatchingRuleId OPTIONAL,
|
||||
# reverseOrder [1] BOOLEAN DEFAULT FALSE }
|
||||
|
||||
|
||||
class SortKeyType(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('attributeType', univ.OctetString()),
|
||||
namedtype.OptionalNamedType('orderingRule',
|
||||
univ.OctetString().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
|
||||
)
|
||||
),
|
||||
namedtype.DefaultedNamedType('reverseOrder', univ.Boolean(False).subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))))
|
||||
|
||||
|
||||
class SortKeyListType(univ.SequenceOf):
|
||||
componentType = SortKeyType()
|
||||
|
||||
|
||||
class SSSRequestControl(RequestControl):
|
||||
'''Order result server side
|
||||
|
||||
>>> s = SSSRequestControl(ordering_rules=['-cn'])
|
||||
'''
|
||||
controlType = '1.2.840.113556.1.4.473'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
criticality=False,
|
||||
ordering_rules=None,
|
||||
):
|
||||
RequestControl.__init__(self,self.controlType,criticality)
|
||||
self.ordering_rules = ordering_rules
|
||||
if isinstance(ordering_rules, basestring):
|
||||
ordering_rules = [ordering_rules]
|
||||
for rule in ordering_rules:
|
||||
rule = rule.split(':')
|
||||
assert len(rule) < 3, 'syntax for ordering rule: [-]<attribute-type>[:ordering-rule]'
|
||||
|
||||
def asn1(self):
|
||||
p = SortKeyListType()
|
||||
for i, rule in enumerate(self.ordering_rules):
|
||||
q = SortKeyType()
|
||||
reverse_order = rule.startswith('-')
|
||||
if reverse_order:
|
||||
rule = rule[1:]
|
||||
if ':' in rule:
|
||||
attribute_type, ordering_rule = rule.split(':')
|
||||
else:
|
||||
attribute_type, ordering_rule = rule, None
|
||||
q.setComponentByName('attributeType', attribute_type)
|
||||
if ordering_rule:
|
||||
q.setComponentByName('orderingRule', ordering_rule)
|
||||
if reverse_order:
|
||||
q.setComponentByName('reverseOrder', 1)
|
||||
p.setComponentByPosition(i, q)
|
||||
return p
|
||||
|
||||
def encodeControlValue(self):
|
||||
return encoder.encode(self.asn1())
|
||||
|
||||
|
||||
class SortResultType(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('sortResult', univ.Enumerated().subtype(
|
||||
namedValues=namedval.NamedValues(
|
||||
('success', 0),
|
||||
('operationsError', 1),
|
||||
('timeLimitExceeded', 3),
|
||||
('strongAuthRequired', 8),
|
||||
('adminLimitExceeded', 11),
|
||||
('noSuchAttribute', 16),
|
||||
('inappropriateMatching', 18),
|
||||
('insufficientAccessRights', 50),
|
||||
('busy', 51),
|
||||
('unwillingToPerform', 53),
|
||||
('other', 80)),
|
||||
subtypeSpec=univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(
|
||||
0, 1, 3, 8, 11, 16, 18, 50, 51, 53, 80))),
|
||||
namedtype.OptionalNamedType('attributeType',
|
||||
univ.OctetString().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
class SSSResponseControl(ResponseControl):
|
||||
controlType = '1.2.840.113556.1.4.474'
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
ResponseControl.__init__(self,self.controlType,criticality)
|
||||
|
||||
def decodeControlValue(self, encoded):
|
||||
p, rest = decoder.decode(encoded, asn1Spec=SortResultType())
|
||||
assert not rest, 'all data could not be decoded'
|
||||
sort_result = p.getComponentByName('sortResult')
|
||||
self.sortResult = int(sort_result)
|
||||
attribute_type = p.getComponentByName('attributeType')
|
||||
if attribute_type.hasValue():
|
||||
self.attributeType = attribute_type
|
||||
else:
|
||||
self.attributeType = None
|
||||
# backward compatibility class attributes
|
||||
self.result = self.sortResult
|
||||
self.attribute_type_error = self.attributeType
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SSSResponseControl.controlType] = SSSResponseControl
|
143
thesisenv/lib/python3.6/site-packages/ldap/controls/vlv.py
Normal file
143
thesisenv/lib/python3.6/site-packages/ldap/controls/vlv.py
Normal file
@ -0,0 +1,143 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.controls.vlv - classes for Virtual List View
|
||||
(see draft-ietf-ldapext-ldapv3-vlv)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'VLVRequestControl',
|
||||
'VLVResponseControl',
|
||||
]
|
||||
|
||||
import ldap
|
||||
from ldap.ldapobject import LDAPObject
|
||||
from ldap.controls import (RequestControl, ResponseControl,
|
||||
KNOWN_RESPONSE_CONTROLS, DecodeControlTuples)
|
||||
|
||||
from pyasn1.type import univ, namedtype, tag, namedval, constraint
|
||||
from pyasn1.codec.ber import encoder, decoder
|
||||
|
||||
|
||||
class ByOffsetType(univ.Sequence):
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('offset', univ.Integer()),
|
||||
namedtype.NamedType('contentCount', univ.Integer()))
|
||||
|
||||
|
||||
class TargetType(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('byOffset', ByOffsetType()),
|
||||
namedtype.NamedType('greaterThanOrEqual', univ.OctetString().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,
|
||||
tag.tagFormatSimple, 1))))
|
||||
|
||||
|
||||
class VirtualListViewRequestType(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('beforeCount', univ.Integer()),
|
||||
namedtype.NamedType('afterCount', univ.Integer()),
|
||||
namedtype.NamedType('target', TargetType()),
|
||||
namedtype.OptionalNamedType('contextID', univ.OctetString()))
|
||||
|
||||
|
||||
class VLVRequestControl(RequestControl):
|
||||
controlType = '2.16.840.1.113730.3.4.9'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
criticality=False,
|
||||
before_count=0,
|
||||
after_count=0,
|
||||
offset=None,
|
||||
content_count=None,
|
||||
greater_than_or_equal=None,
|
||||
context_id=None,
|
||||
):
|
||||
RequestControl.__init__(self,self.controlType,criticality)
|
||||
assert (offset is not None and content_count is not None) or \
|
||||
greater_than_or_equal, \
|
||||
ValueError(
|
||||
'offset and content_count must be set together or greater_than_or_equal must be used'
|
||||
)
|
||||
self.before_count = before_count
|
||||
self.after_count = after_count
|
||||
self.offset = offset
|
||||
self.content_count = content_count
|
||||
self.greater_than_or_equal = greater_than_or_equal
|
||||
self.context_id = context_id
|
||||
|
||||
def encodeControlValue(self):
|
||||
p = VirtualListViewRequestType()
|
||||
p.setComponentByName('beforeCount', self.before_count)
|
||||
p.setComponentByName('afterCount', self.after_count)
|
||||
if self.offset is not None and self.content_count is not None:
|
||||
by_offset = ByOffsetType()
|
||||
by_offset.setComponentByName('offset', self.offset)
|
||||
by_offset.setComponentByName('contentCount', self.content_count)
|
||||
target = TargetType()
|
||||
target.setComponentByName('byOffset', by_offset)
|
||||
elif self.greater_than_or_equal:
|
||||
target = TargetType()
|
||||
target.setComponentByName('greaterThanOrEqual',
|
||||
self.greater_than_or_equal)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
p.setComponentByName('target', target)
|
||||
return encoder.encode(p)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[VLVRequestControl.controlType] = VLVRequestControl
|
||||
|
||||
|
||||
class VirtualListViewResultType(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
('success', 0),
|
||||
('operationsError', 1),
|
||||
('protocolError', 3),
|
||||
('unwillingToPerform', 53),
|
||||
('insufficientAccessRights', 50),
|
||||
('adminLimitExceeded', 11),
|
||||
('innapropriateMatching', 18),
|
||||
('sortControlMissing', 60),
|
||||
('offsetRangeError', 61),
|
||||
('other', 80),
|
||||
)
|
||||
|
||||
|
||||
class VirtualListViewResponseType(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('targetPosition', univ.Integer()),
|
||||
namedtype.NamedType('contentCount', univ.Integer()),
|
||||
namedtype.NamedType('virtualListViewResult',
|
||||
VirtualListViewResultType()),
|
||||
namedtype.OptionalNamedType('contextID', univ.OctetString()))
|
||||
|
||||
|
||||
class VLVResponseControl(ResponseControl):
|
||||
controlType = '2.16.840.1.113730.3.4.10'
|
||||
|
||||
def __init__(self,criticality=False):
|
||||
ResponseControl.__init__(self,self.controlType,criticality)
|
||||
|
||||
def decodeControlValue(self,encoded):
|
||||
p, rest = decoder.decode(encoded, asn1Spec=VirtualListViewResponseType())
|
||||
assert not rest, 'all data could not be decoded'
|
||||
self.targetPosition = int(p.getComponentByName('targetPosition'))
|
||||
self.contentCount = int(p.getComponentByName('contentCount'))
|
||||
virtual_list_view_result = p.getComponentByName('virtualListViewResult')
|
||||
self.virtualListViewResult = int(virtual_list_view_result)
|
||||
context_id = p.getComponentByName('contextID')
|
||||
if context_id.hasValue():
|
||||
self.contextID = str(context_id)
|
||||
else:
|
||||
self.contextID = None
|
||||
# backward compatibility class attributes
|
||||
self.target_position = self.targetPosition
|
||||
self.content_count = self.contentCount
|
||||
self.result = self.virtualListViewResult
|
||||
self.context_id = self.contextID
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[VLVResponseControl.controlType] = VLVResponseControl
|
122
thesisenv/lib/python3.6/site-packages/ldap/dn.py
Normal file
122
thesisenv/lib/python3.6/site-packages/ldap/dn.py
Normal file
@ -0,0 +1,122 @@
|
||||
"""
|
||||
dn.py - misc stuff for handling distinguished names (see RFC 4514)
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from ldap.pkginfo import __version__
|
||||
|
||||
import _ldap
|
||||
assert _ldap.__version__==__version__, \
|
||||
ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__))
|
||||
|
||||
import ldap.functions
|
||||
|
||||
|
||||
def escape_dn_chars(s):
|
||||
"""
|
||||
Escape all DN special characters found in s
|
||||
with a back-slash (see RFC 4514, section 2.4)
|
||||
"""
|
||||
if s:
|
||||
s = s.replace('\\','\\\\')
|
||||
s = s.replace(',' ,'\\,')
|
||||
s = s.replace('+' ,'\\+')
|
||||
s = s.replace('"' ,'\\"')
|
||||
s = s.replace('<' ,'\\<')
|
||||
s = s.replace('>' ,'\\>')
|
||||
s = s.replace(';' ,'\\;')
|
||||
s = s.replace('=' ,'\\=')
|
||||
s = s.replace('\000' ,'\\\000')
|
||||
if s[0]=='#' or s[0]==' ':
|
||||
s = ''.join(('\\',s))
|
||||
if s[-1]==' ':
|
||||
s = ''.join((s[:-1],'\\ '))
|
||||
return s
|
||||
|
||||
|
||||
def str2dn(dn,flags=0):
|
||||
"""
|
||||
This function takes a DN as string as parameter and returns
|
||||
a decomposed DN. It's the inverse to dn2str().
|
||||
|
||||
flags describes the format of the dn
|
||||
|
||||
See also the OpenLDAP man-page ldap_str2dn(3)
|
||||
"""
|
||||
if not dn:
|
||||
return []
|
||||
if sys.version_info[0] < 3 and isinstance(dn, unicode):
|
||||
dn = dn.encode('utf-8')
|
||||
return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags)
|
||||
|
||||
|
||||
def dn2str(dn):
|
||||
"""
|
||||
This function takes a decomposed DN as parameter and returns
|
||||
a single string. It's the inverse to str2dn() but will always
|
||||
return a DN in LDAPv3 format compliant to RFC 4514.
|
||||
"""
|
||||
return ','.join([
|
||||
'+'.join([
|
||||
'='.join((atype,escape_dn_chars(avalue or '')))
|
||||
for atype,avalue,dummy in rdn])
|
||||
for rdn in dn
|
||||
])
|
||||
|
||||
def explode_dn(dn, notypes=False, flags=0):
|
||||
"""
|
||||
explode_dn(dn [, notypes=False [, flags=0]]) -> list
|
||||
|
||||
This function takes a DN and breaks it up into its component parts.
|
||||
The notypes parameter is used to specify that only the component's
|
||||
attribute values be returned and not the attribute types.
|
||||
"""
|
||||
if not dn:
|
||||
return []
|
||||
dn_decomp = str2dn(dn,flags)
|
||||
rdn_list = []
|
||||
for rdn in dn_decomp:
|
||||
if notypes:
|
||||
rdn_list.append('+'.join([
|
||||
escape_dn_chars(avalue or '')
|
||||
for atype,avalue,dummy in rdn
|
||||
]))
|
||||
else:
|
||||
rdn_list.append('+'.join([
|
||||
'='.join((atype,escape_dn_chars(avalue or '')))
|
||||
for atype,avalue,dummy in rdn
|
||||
]))
|
||||
return rdn_list
|
||||
|
||||
|
||||
def explode_rdn(rdn, notypes=False, flags=0):
|
||||
"""
|
||||
explode_rdn(rdn [, notypes=0 [, flags=0]]) -> list
|
||||
|
||||
This function takes a RDN and breaks it up into its component parts
|
||||
if it is a multi-valued RDN.
|
||||
The notypes parameter is used to specify that only the component's
|
||||
attribute values be returned and not the attribute types.
|
||||
"""
|
||||
if not rdn:
|
||||
return []
|
||||
rdn_decomp = str2dn(rdn,flags)[0]
|
||||
if notypes:
|
||||
return [avalue or '' for atype,avalue,dummy in rdn_decomp]
|
||||
else:
|
||||
return ['='.join((atype,escape_dn_chars(avalue or ''))) for atype,avalue,dummy in rdn_decomp]
|
||||
|
||||
|
||||
def is_dn(s,flags=0):
|
||||
"""
|
||||
Returns True is `s' can be parsed by ldap.dn.str2dn() like as a
|
||||
distinguished host_name (DN), otherwise False is returned.
|
||||
"""
|
||||
try:
|
||||
str2dn(s,flags)
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
return True
|
67
thesisenv/lib/python3.6/site-packages/ldap/extop/__init__.py
Normal file
67
thesisenv/lib/python3.6/site-packages/ldap/extop/__init__.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""
|
||||
controls.py - support classes for LDAPv3 extended operations
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
|
||||
Description:
|
||||
The ldap.extop module provides base classes for LDAPv3 extended operations.
|
||||
Each class provides support for a certain extended operation request and
|
||||
response.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
|
||||
class ExtendedRequest:
|
||||
"""
|
||||
Generic base class for a LDAPv3 extended operation request
|
||||
|
||||
requestName
|
||||
OID as string of the LDAPv3 extended operation request
|
||||
requestValue
|
||||
value of the LDAPv3 extended operation request
|
||||
(here it is the BER-encoded ASN.1 request value)
|
||||
"""
|
||||
|
||||
def __init__(self,requestName,requestValue):
|
||||
self.requestName = requestName
|
||||
self.requestValue = requestValue
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s,%s)' % (self.__class__.__name__,self.requestName,self.requestValue)
|
||||
|
||||
def encodedRequestValue(self):
|
||||
"""
|
||||
returns the BER-encoded ASN.1 request value composed by class attributes
|
||||
set before
|
||||
"""
|
||||
return self.requestValue
|
||||
|
||||
|
||||
class ExtendedResponse:
|
||||
"""
|
||||
Generic base class for a LDAPv3 extended operation response
|
||||
|
||||
requestName
|
||||
OID as string of the LDAPv3 extended operation response
|
||||
encodedResponseValue
|
||||
BER-encoded ASN.1 value of the LDAPv3 extended operation response
|
||||
"""
|
||||
|
||||
def __init__(self,responseName,encodedResponseValue):
|
||||
self.responseName = responseName
|
||||
self.responseValue = self.decodeResponseValue(encodedResponseValue)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s,%s)' % (self.__class__.__name__,self.responseName,self.responseValue)
|
||||
|
||||
def decodeResponseValue(self,value):
|
||||
"""
|
||||
decodes the BER-encoded ASN.1 extended operation response value and
|
||||
sets the appropriate class attributes
|
||||
"""
|
||||
return value
|
||||
|
||||
|
||||
# Import sub-modules
|
||||
from ldap.extop.dds import *
|
75
thesisenv/lib/python3.6/site-packages/ldap/extop/dds.py
Normal file
75
thesisenv/lib/python3.6/site-packages/ldap/extop/dds.py
Normal file
@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.extop.dds - Classes for Dynamic Entries extended operations
|
||||
(see RFC 2589)
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap.extop import ExtendedRequest,ExtendedResponse
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import namedtype,univ,tag
|
||||
from pyasn1.codec.der import encoder,decoder
|
||||
from pyasn1_modules.rfc2251 import LDAPDN
|
||||
|
||||
|
||||
class RefreshRequest(ExtendedRequest):
|
||||
|
||||
requestName = '1.3.6.1.4.1.1466.101.119.1'
|
||||
defaultRequestTtl = 86400
|
||||
|
||||
class RefreshRequestValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'entryName',
|
||||
LDAPDN().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,0)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'requestTtl',
|
||||
univ.Integer().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self,requestName=None,entryName=None,requestTtl=None):
|
||||
self.entryName = entryName
|
||||
self.requestTtl = requestTtl or self.defaultRequestTtl
|
||||
|
||||
def encodedRequestValue(self):
|
||||
p = self.RefreshRequestValue()
|
||||
p.setComponentByName(
|
||||
'entryName',
|
||||
LDAPDN(self.entryName).subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple,0)
|
||||
)
|
||||
)
|
||||
p.setComponentByName(
|
||||
'requestTtl',
|
||||
univ.Integer(self.requestTtl).subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
|
||||
)
|
||||
)
|
||||
return encoder.encode(p)
|
||||
|
||||
|
||||
class RefreshResponse(ExtendedResponse):
|
||||
responseName = '1.3.6.1.4.1.1466.101.119.1'
|
||||
|
||||
class RefreshResponseValue(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'responseTtl',
|
||||
univ.Integer().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatSimple,1)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def decodeResponseValue(self,value):
|
||||
respValue,_ = decoder.decode(value,asn1Spec=self.RefreshResponseValue())
|
||||
self.responseTtl = int(respValue.getComponentByName('responseTtl'))
|
||||
return self.responseTtl
|
89
thesisenv/lib/python3.6/site-packages/ldap/filter.py
Normal file
89
thesisenv/lib/python3.6/site-packages/ldap/filter.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""
|
||||
filters.py - misc stuff for handling LDAP filter strings (see RFC2254)
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
|
||||
Compatibility:
|
||||
- Tested with Python 2.0+
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
from ldap.functions import strf_secs
|
||||
|
||||
import time
|
||||
|
||||
|
||||
def escape_filter_chars(assertion_value,escape_mode=0):
|
||||
"""
|
||||
Replace all special characters found in assertion_value
|
||||
by quoted notation.
|
||||
|
||||
escape_mode
|
||||
If 0 only special chars mentioned in RFC 4515 are escaped.
|
||||
If 1 all NON-ASCII chars are escaped.
|
||||
If 2 all chars are escaped.
|
||||
"""
|
||||
if escape_mode:
|
||||
r = []
|
||||
if escape_mode==1:
|
||||
for c in assertion_value:
|
||||
if c < '0' or c > 'z' or c in "\\*()":
|
||||
c = "\\%02x" % ord(c)
|
||||
r.append(c)
|
||||
elif escape_mode==2:
|
||||
for c in assertion_value:
|
||||
r.append("\\%02x" % ord(c))
|
||||
else:
|
||||
raise ValueError('escape_mode must be 0, 1 or 2.')
|
||||
s = ''.join(r)
|
||||
else:
|
||||
s = assertion_value.replace('\\', r'\5c')
|
||||
s = s.replace(r'*', r'\2a')
|
||||
s = s.replace(r'(', r'\28')
|
||||
s = s.replace(r')', r'\29')
|
||||
s = s.replace('\x00', r'\00')
|
||||
return s
|
||||
|
||||
|
||||
def filter_format(filter_template,assertion_values):
|
||||
"""
|
||||
filter_template
|
||||
String containing %s as placeholder for assertion values.
|
||||
assertion_values
|
||||
List or tuple of assertion values. Length must match
|
||||
count of %s in filter_template.
|
||||
"""
|
||||
return filter_template % tuple(escape_filter_chars(v) for v in assertion_values)
|
||||
|
||||
|
||||
def time_span_filter(
|
||||
filterstr='',
|
||||
from_timestamp=0,
|
||||
until_timestamp=None,
|
||||
delta_attr='modifyTimestamp',
|
||||
):
|
||||
"""
|
||||
If last_run_timestr is non-zero filterstr will be extended
|
||||
"""
|
||||
if until_timestamp is None:
|
||||
until_timestamp = time.time()
|
||||
if from_timestamp < 0:
|
||||
from_timestamp = until_timestamp + from_timestamp
|
||||
if from_timestamp > until_timestamp:
|
||||
raise ValueError('from_timestamp %r must not be greater than until_timestamp %r' % (
|
||||
from_timestamp, until_timestamp
|
||||
))
|
||||
return (
|
||||
'(&'
|
||||
'{filterstr}'
|
||||
'({delta_attr}>={from_timestr})'
|
||||
'(!({delta_attr}>={until_timestr}))'
|
||||
')'
|
||||
).format(
|
||||
filterstr=filterstr,
|
||||
delta_attr=delta_attr,
|
||||
from_timestr=strf_secs(from_timestamp),
|
||||
until_timestr=strf_secs(until_timestamp),
|
||||
)
|
||||
# end of time_span_filter()
|
125
thesisenv/lib/python3.6/site-packages/ldap/functions.py
Normal file
125
thesisenv/lib/python3.6/site-packages/ldap/functions.py
Normal file
@ -0,0 +1,125 @@
|
||||
"""
|
||||
functions.py - wraps functions of module _ldap
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
__all__ = [
|
||||
'open','initialize','init',
|
||||
'explode_dn','explode_rdn',
|
||||
'get_option','set_option',
|
||||
'escape_str',
|
||||
'strf_secs','strp_secs',
|
||||
]
|
||||
|
||||
import sys,pprint,time,_ldap,ldap
|
||||
from calendar import timegm
|
||||
|
||||
from ldap import LDAPError
|
||||
|
||||
from ldap.dn import explode_dn,explode_rdn
|
||||
|
||||
from ldap.ldapobject import LDAPObject
|
||||
|
||||
if __debug__:
|
||||
# Tracing is only supported in debugging mode
|
||||
import traceback
|
||||
|
||||
# See _raise_byteswarning in ldapobject.py
|
||||
_LDAP_WARN_SKIP_FRAME = True
|
||||
|
||||
|
||||
def _ldap_function_call(lock,func,*args,**kwargs):
|
||||
"""
|
||||
Wrapper function which locks and logs calls to function
|
||||
|
||||
lock
|
||||
Instance of threading.Lock or compatible
|
||||
func
|
||||
Function to call with arguments passed in via *args and **kwargs
|
||||
"""
|
||||
if lock:
|
||||
lock.acquire()
|
||||
if __debug__:
|
||||
if ldap._trace_level>=1:
|
||||
ldap._trace_file.write('*** %s.%s %s\n' % (
|
||||
'_ldap',func.__name__,
|
||||
pprint.pformat((args,kwargs))
|
||||
))
|
||||
if ldap._trace_level>=9:
|
||||
traceback.print_stack(limit=ldap._trace_stack_limit,file=ldap._trace_file)
|
||||
try:
|
||||
try:
|
||||
result = func(*args,**kwargs)
|
||||
finally:
|
||||
if lock:
|
||||
lock.release()
|
||||
except LDAPError as e:
|
||||
if __debug__ and ldap._trace_level>=2:
|
||||
ldap._trace_file.write('=> LDAPError: %s\n' % (str(e)))
|
||||
raise
|
||||
if __debug__ and ldap._trace_level>=2:
|
||||
ldap._trace_file.write('=> result:\n%s\n' % (pprint.pformat(result)))
|
||||
return result
|
||||
|
||||
|
||||
def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, bytes_mode=None):
|
||||
"""
|
||||
Return LDAPObject instance by opening LDAP connection to
|
||||
LDAP host specified by LDAP URL
|
||||
|
||||
Parameters:
|
||||
uri
|
||||
LDAP URL containing at least connection scheme and hostport,
|
||||
e.g. ldap://localhost:389
|
||||
trace_level
|
||||
If non-zero a trace output of LDAP calls is generated.
|
||||
trace_file
|
||||
File object where to write the trace output to.
|
||||
Default is to use stdout.
|
||||
bytes_mode
|
||||
Whether to enable :ref:`bytes_mode` for backwards compatibility under Py2.
|
||||
"""
|
||||
return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode)
|
||||
|
||||
|
||||
def get_option(option):
|
||||
"""
|
||||
get_option(name) -> value
|
||||
|
||||
Get the value of an LDAP global option.
|
||||
"""
|
||||
return _ldap_function_call(None,_ldap.get_option,option)
|
||||
|
||||
|
||||
def set_option(option,invalue):
|
||||
"""
|
||||
set_option(name, value)
|
||||
|
||||
Set the value of an LDAP global option.
|
||||
"""
|
||||
return _ldap_function_call(None,_ldap.set_option,option,invalue)
|
||||
|
||||
|
||||
def escape_str(escape_func,s,*args):
|
||||
"""
|
||||
Applies escape_func() to all items of `args' and returns a string based
|
||||
on format string `s'.
|
||||
"""
|
||||
return s % tuple(escape_func(v) for v in args)
|
||||
|
||||
|
||||
def strf_secs(secs):
|
||||
"""
|
||||
Convert seconds since epoch to a string compliant to LDAP syntax GeneralizedTime
|
||||
"""
|
||||
return time.strftime('%Y%m%d%H%M%SZ', time.gmtime(secs))
|
||||
|
||||
|
||||
def strp_secs(dt_str):
|
||||
"""
|
||||
Convert LDAP syntax GeneralizedTime to seconds since epoch
|
||||
"""
|
||||
return timegm(time.strptime(dt_str, '%Y%m%d%H%M%SZ'))
|
1267
thesisenv/lib/python3.6/site-packages/ldap/ldapobject.py
Normal file
1267
thesisenv/lib/python3.6/site-packages/ldap/ldapobject.py
Normal file
File diff suppressed because it is too large
Load Diff
19
thesisenv/lib/python3.6/site-packages/ldap/logger.py
Normal file
19
thesisenv/lib/python3.6/site-packages/ldap/logger.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Helper class for using logging as trace file object
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
class logging_file_class(object):
|
||||
|
||||
def __init__(self, logging_level):
|
||||
self._logging_level = logging_level
|
||||
|
||||
def write(self, msg):
|
||||
logging.log(self._logging_level, msg[:-1])
|
||||
|
||||
def flush(self):
|
||||
return
|
||||
|
||||
logging_file_obj = logging_file_class(logging.DEBUG)
|
97
thesisenv/lib/python3.6/site-packages/ldap/modlist.py
Normal file
97
thesisenv/lib/python3.6/site-packages/ldap/modlist.py
Normal file
@ -0,0 +1,97 @@
|
||||
"""
|
||||
ldap.modlist - create add/modify modlist's
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
import ldap
|
||||
|
||||
|
||||
def addModlist(entry,ignore_attr_types=None):
|
||||
"""Build modify list for call of method LDAPObject.add()"""
|
||||
ignore_attr_types = {v.lower() for v in ignore_attr_types or []}
|
||||
modlist = []
|
||||
for attrtype, value in entry.items():
|
||||
if attrtype.lower() in ignore_attr_types:
|
||||
# This attribute type is ignored
|
||||
continue
|
||||
# Eliminate empty attr value strings in list
|
||||
attrvaluelist = [item for item in value if item is not None]
|
||||
if attrvaluelist:
|
||||
modlist.append((attrtype, value))
|
||||
return modlist # addModlist()
|
||||
|
||||
|
||||
def modifyModlist(
|
||||
old_entry,new_entry,ignore_attr_types=None,ignore_oldexistent=0,case_ignore_attr_types=None
|
||||
):
|
||||
"""
|
||||
Build differential modify list for calling LDAPObject.modify()/modify_s()
|
||||
|
||||
old_entry
|
||||
Dictionary holding the old entry
|
||||
new_entry
|
||||
Dictionary holding what the new entry should be
|
||||
ignore_attr_types
|
||||
List of attribute type names to be ignored completely
|
||||
ignore_oldexistent
|
||||
If non-zero attribute type names which are in old_entry
|
||||
but are not found in new_entry at all are not deleted.
|
||||
This is handy for situations where your application
|
||||
sets attribute value to '' for deleting an attribute.
|
||||
In most cases leave zero.
|
||||
case_ignore_attr_types
|
||||
List of attribute type names for which comparison will be made
|
||||
case-insensitive
|
||||
"""
|
||||
ignore_attr_types = {v.lower() for v in ignore_attr_types or []}
|
||||
case_ignore_attr_types = {v.lower() for v in case_ignore_attr_types or []}
|
||||
modlist = []
|
||||
attrtype_lower_map = {}
|
||||
for a in old_entry.keys():
|
||||
attrtype_lower_map[a.lower()]=a
|
||||
for attrtype, value in new_entry.items():
|
||||
attrtype_lower = attrtype.lower()
|
||||
if attrtype_lower in ignore_attr_types:
|
||||
# This attribute type is ignored
|
||||
continue
|
||||
# Filter away null-strings
|
||||
new_value = [item for item in value if item is not None]
|
||||
if attrtype_lower in attrtype_lower_map:
|
||||
old_value = old_entry.get(attrtype_lower_map[attrtype_lower],[])
|
||||
old_value = [item for item in old_value if item is not None]
|
||||
del attrtype_lower_map[attrtype_lower]
|
||||
else:
|
||||
old_value = []
|
||||
if not old_value and new_value:
|
||||
# Add a new attribute to entry
|
||||
modlist.append((ldap.MOD_ADD,attrtype,new_value))
|
||||
elif old_value and new_value:
|
||||
# Replace existing attribute
|
||||
replace_attr_value = len(old_value)!=len(new_value)
|
||||
if not replace_attr_value:
|
||||
if attrtype_lower in case_ignore_attr_types:
|
||||
old_value_set = {v.lower() for v in old_value}
|
||||
new_value_set = {v.lower() for v in new_value}
|
||||
else:
|
||||
old_value_set = set(old_value)
|
||||
new_value_set = set(new_value)
|
||||
replace_attr_value = new_value_set != old_value_set
|
||||
if replace_attr_value:
|
||||
modlist.append((ldap.MOD_DELETE,attrtype,None))
|
||||
modlist.append((ldap.MOD_ADD,attrtype,new_value))
|
||||
elif old_value and not new_value:
|
||||
# Completely delete an existing attribute
|
||||
modlist.append((ldap.MOD_DELETE,attrtype,None))
|
||||
if not ignore_oldexistent:
|
||||
# Remove all attributes of old_entry which are not present
|
||||
# in new_entry at all
|
||||
for a, val in attrtype_lower_map.items():
|
||||
if a in ignore_attr_types:
|
||||
# This attribute type is ignored
|
||||
continue
|
||||
attrtype = val
|
||||
modlist.append((ldap.MOD_DELETE,attrtype,None))
|
||||
return modlist # modifyModlist()
|
7
thesisenv/lib/python3.6/site-packages/ldap/pkginfo.py
Normal file
7
thesisenv/lib/python3.6/site-packages/ldap/pkginfo.py
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
meta attributes for packaging which does not import any dependencies
|
||||
"""
|
||||
__version__ = '3.1.0'
|
||||
__author__ = u'python-ldap project'
|
||||
__license__ = 'Python style'
|
41
thesisenv/lib/python3.6/site-packages/ldap/resiter.py
Normal file
41
thesisenv/lib/python3.6/site-packages/ldap/resiter.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
ldap.resiter - processing LDAP results with iterators
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap.pkginfo import __version__, __author__, __license__
|
||||
|
||||
|
||||
class ResultProcessor:
|
||||
"""
|
||||
Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes.
|
||||
"""
|
||||
|
||||
def allresults(self, msgid, timeout=-1, add_ctrls=0):
|
||||
"""
|
||||
Generator function which returns an iterator for processing all LDAP operation
|
||||
results of the given msgid like retrieved with LDAPObject.result3() -> 4-tuple
|
||||
"""
|
||||
result_type, result_list, result_msgid, result_serverctrls, _, _ = \
|
||||
self.result4(
|
||||
msgid,
|
||||
0,
|
||||
timeout,
|
||||
add_ctrls=add_ctrls
|
||||
)
|
||||
while result_type and result_list:
|
||||
yield (
|
||||
result_type,
|
||||
result_list,
|
||||
result_msgid,
|
||||
result_serverctrls
|
||||
)
|
||||
result_type, result_list, result_msgid, result_serverctrls, _, _ = \
|
||||
self.result4(
|
||||
msgid,
|
||||
0,
|
||||
timeout,
|
||||
add_ctrls=add_ctrls
|
||||
)
|
||||
return # allresults()
|
134
thesisenv/lib/python3.6/site-packages/ldap/sasl.py
Normal file
134
thesisenv/lib/python3.6/site-packages/ldap/sasl.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""
|
||||
sasl.py - support for SASL mechanism
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
|
||||
Description:
|
||||
The ldap.sasl module provides SASL authentication classes.
|
||||
Each class provides support for one SASL mechanism. This is done by
|
||||
implementing a callback() - method, which will be called by the
|
||||
LDAPObject's sasl_bind_s() method
|
||||
Implementing support for new sasl mechanism is very easy --- see
|
||||
the examples of digest_md5 and gssapi.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
if __debug__:
|
||||
# Tracing is only supported in debugging mode
|
||||
from ldap import _trace_level, _trace_file
|
||||
|
||||
|
||||
# These are the SASL callback id's , as defined in sasl.h
|
||||
CB_USER = 0x4001
|
||||
CB_AUTHNAME = 0x4002
|
||||
CB_LANGUAGE = 0x4003
|
||||
CB_PASS = 0x4004
|
||||
CB_ECHOPROMPT = 0x4005
|
||||
CB_NOECHOPROMPT = 0x4006
|
||||
CB_GETREALM = 0x4008
|
||||
|
||||
|
||||
class sasl:
|
||||
"""
|
||||
This class handles SASL interactions for authentication.
|
||||
If an instance of this class is passed to ldap's sasl_bind_s()
|
||||
method, the library will call its callback() method. For
|
||||
specific SASL authentication mechanisms, this method can be
|
||||
overridden
|
||||
"""
|
||||
|
||||
def __init__(self, cb_value_dict, mech):
|
||||
"""
|
||||
The (generic) base class takes a cb_value_dictionary of
|
||||
question-answer pairs. Questions are specified by the respective
|
||||
SASL callback id's. The mech argument is a string that specifies
|
||||
the SASL mechaninsm to be uesd.
|
||||
"""
|
||||
self.cb_value_dict = cb_value_dict or {}
|
||||
if not isinstance(mech, bytes):
|
||||
mech = mech.encode('utf-8')
|
||||
self.mech = mech
|
||||
|
||||
def callback(self, cb_id, challenge, prompt, defresult):
|
||||
"""
|
||||
The callback method will be called by the sasl_bind_s()
|
||||
method several times. Each time it will provide the id, which
|
||||
tells us what kind of information is requested (the CB_*
|
||||
constants above). The challenge might be a short (English) text
|
||||
or some binary string, from which the return value is calculated.
|
||||
The prompt argument is always a human-readable description string;
|
||||
The defresult is a default value provided by the sasl library
|
||||
|
||||
Currently, we do not use the challenge and prompt information, and
|
||||
return only information which is stored in the self.cb_value_dict
|
||||
cb_value_dictionary. Note that the current callback interface is not very
|
||||
useful for writing generic sasl GUIs, which would need to know all
|
||||
the questions to ask, before the answers are returned to the sasl
|
||||
lib (in contrast to one question at a time).
|
||||
|
||||
Unicode strings are always converted to bytes.
|
||||
"""
|
||||
|
||||
# The following print command might be useful for debugging
|
||||
# new sasl mechanisms. So it is left here
|
||||
cb_result = self.cb_value_dict.get(cb_id, defresult) or ''
|
||||
if __debug__:
|
||||
if _trace_level >= 1:
|
||||
_trace_file.write("*** id=%d, challenge=%s, prompt=%s, defresult=%s\n-> %s\n" % (
|
||||
cb_id,
|
||||
challenge,
|
||||
prompt,
|
||||
repr(defresult),
|
||||
repr(self.cb_value_dict.get(cb_result))
|
||||
))
|
||||
if not isinstance(cb_result, bytes):
|
||||
cb_result = cb_result.encode('utf-8')
|
||||
return cb_result
|
||||
|
||||
|
||||
class cram_md5(sasl):
|
||||
"""
|
||||
This class handles SASL CRAM-MD5 authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, authc_id, password, authz_id=""):
|
||||
auth_dict = {
|
||||
CB_AUTHNAME: authc_id,
|
||||
CB_PASS: password,
|
||||
CB_USER: authz_id,
|
||||
}
|
||||
sasl.__init__(self, auth_dict, "CRAM-MD5")
|
||||
|
||||
|
||||
class digest_md5(sasl):
|
||||
"""
|
||||
This class handles SASL DIGEST-MD5 authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, authc_id, password, authz_id=""):
|
||||
auth_dict = {
|
||||
CB_AUTHNAME: authc_id,
|
||||
CB_PASS: password,
|
||||
CB_USER: authz_id,
|
||||
}
|
||||
sasl.__init__(self, auth_dict, "DIGEST-MD5")
|
||||
|
||||
|
||||
class gssapi(sasl):
|
||||
"""
|
||||
This class handles SASL GSSAPI (i.e. Kerberos V) authentication.
|
||||
"""
|
||||
|
||||
def __init__(self, authz_id=""):
|
||||
sasl.__init__(self, {CB_USER: authz_id}, "GSSAPI")
|
||||
|
||||
|
||||
class external(sasl):
|
||||
"""
|
||||
This class handles SASL EXTERNAL authentication
|
||||
(i.e. X.509 client certificate)
|
||||
"""
|
||||
|
||||
def __init__(self, authz_id=""):
|
||||
sasl.__init__(self, {CB_USER: authz_id}, "EXTERNAL")
|
@ -0,0 +1,10 @@
|
||||
"""
|
||||
ldap.schema - LDAPv3 schema handling
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
from ldap import __version__
|
||||
|
||||
from ldap.schema.subentry import SubSchema,SCHEMA_ATTRS,SCHEMA_CLASS_MAPPING,SCHEMA_ATTR_MAPPING,urlfetch
|
||||
from ldap.schema.models import *
|
701
thesisenv/lib/python3.6/site-packages/ldap/schema/models.py
Normal file
701
thesisenv/lib/python3.6/site-packages/ldap/schema/models.py
Normal file
@ -0,0 +1,701 @@
|
||||
"""
|
||||
schema.py - support for subSchemaSubEntry information
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import ldap.cidict
|
||||
from ldap.compat import IterableUserDict
|
||||
|
||||
from ldap.schema.tokenizer import split_tokens,extract_tokens
|
||||
|
||||
NOT_HUMAN_READABLE_LDAP_SYNTAXES = {
|
||||
'1.3.6.1.4.1.1466.115.121.1.4', # Audio
|
||||
'1.3.6.1.4.1.1466.115.121.1.5', # Binary
|
||||
'1.3.6.1.4.1.1466.115.121.1.8', # Certificate
|
||||
'1.3.6.1.4.1.1466.115.121.1.9', # Certificate List
|
||||
'1.3.6.1.4.1.1466.115.121.1.10', # Certificate Pair
|
||||
'1.3.6.1.4.1.1466.115.121.1.23', # G3 FAX
|
||||
'1.3.6.1.4.1.1466.115.121.1.28', # JPEG
|
||||
'1.3.6.1.4.1.1466.115.121.1.40', # Octet String
|
||||
'1.3.6.1.4.1.1466.115.121.1.49', # Supported Algorithm
|
||||
}
|
||||
|
||||
|
||||
class SchemaElement:
|
||||
"""
|
||||
Base class for all schema element classes. Not used directly!
|
||||
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String which contains the schema element description to be parsed.
|
||||
(Bytestrings are decoded using UTF-8)
|
||||
|
||||
Class attributes:
|
||||
|
||||
schema_attribute
|
||||
LDAP attribute type containing a certain schema element description
|
||||
token_defaults
|
||||
Dictionary internally used by the schema element parser
|
||||
containing the defaults for certain schema description key-words
|
||||
"""
|
||||
token_defaults = {
|
||||
'DESC':(None,),
|
||||
}
|
||||
|
||||
def __init__(self,schema_element_str=None):
|
||||
if sys.version_info >= (3, 0) and isinstance(schema_element_str, bytes):
|
||||
schema_element_str = schema_element_str.decode('utf-8')
|
||||
if schema_element_str:
|
||||
l = split_tokens(schema_element_str)
|
||||
self.set_id(l[1])
|
||||
d = extract_tokens(l,self.token_defaults)
|
||||
self._set_attrs(l,d)
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.desc = d['DESC'][0]
|
||||
return
|
||||
|
||||
def set_id(self,element_id):
|
||||
self.oid = element_id
|
||||
|
||||
def get_id(self):
|
||||
return self.oid
|
||||
|
||||
def key_attr(self,key,value,quoted=0):
|
||||
assert value is None or type(value)==str,TypeError("value has to be of str, was %r" % value)
|
||||
if value:
|
||||
if quoted:
|
||||
return " %s '%s'" % (key,value.replace("'","\\'"))
|
||||
else:
|
||||
return " %s %s" % (key,value)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def key_list(self,key,values,sep=' ',quoted=0):
|
||||
assert type(values)==tuple,TypeError("values has to be a tuple, was %r" % values)
|
||||
if not values:
|
||||
return ''
|
||||
if quoted:
|
||||
quoted_values = [ "'%s'" % value.replace("'","\\'") for value in values ]
|
||||
else:
|
||||
quoted_values = values
|
||||
if len(values)==1:
|
||||
return ' %s %s' % (key,quoted_values[0])
|
||||
else:
|
||||
return ' %s ( %s )' % (key,sep.join(quoted_values))
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class ObjectClass(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an ObjectClassDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID assigned to the object class
|
||||
names
|
||||
This list of strings contains all NAMEs of the object class
|
||||
desc
|
||||
This string contains description text (DESC) of the object class
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the object class is marked
|
||||
as OBSOLETE in the schema
|
||||
must
|
||||
This list of strings contains NAMEs or OIDs of all attributes
|
||||
an entry of the object class must have
|
||||
may
|
||||
This list of strings contains NAMEs or OIDs of additional attributes
|
||||
an entry of the object class may have
|
||||
kind
|
||||
Kind of an object class:
|
||||
0 = STRUCTURAL,
|
||||
1 = ABSTRACT,
|
||||
2 = AUXILIARY
|
||||
sup
|
||||
This list of strings contains NAMEs or OIDs of object classes
|
||||
this object class is derived from
|
||||
"""
|
||||
schema_attribute = u'objectClasses'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'SUP':(()),
|
||||
'STRUCTURAL':None,
|
||||
'AUXILIARY':None,
|
||||
'ABSTRACT':None,
|
||||
'MUST':(()),
|
||||
'MAY':()
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.must = d['MUST']
|
||||
self.may = d['MAY']
|
||||
# Default is STRUCTURAL, see RFC2552 or draft-ietf-ldapbis-syntaxes
|
||||
self.kind = 0
|
||||
if d['ABSTRACT']!=None:
|
||||
self.kind = 1
|
||||
elif d['AUXILIARY']!=None:
|
||||
self.kind = 2
|
||||
if self.kind==0 and not d['SUP'] and self.oid!='2.5.6.0':
|
||||
# STRUCTURAL object classes are sub-classes of 'top' by default
|
||||
self.sup = ('top',)
|
||||
else:
|
||||
self.sup = d['SUP']
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append(self.key_list('SUP',self.sup,sep=' $ '))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append({0:' STRUCTURAL',1:' ABSTRACT',2:' AUXILIARY'}[self.kind])
|
||||
result.append(self.key_list('MUST',self.must,sep=' $ '))
|
||||
result.append(self.key_list('MAY',self.may,sep=' $ '))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
AttributeUsage = ldap.cidict.cidict({
|
||||
'userApplication':0, # work-around for non-compliant schema
|
||||
'userApplications':0,
|
||||
'directoryOperation':1,
|
||||
'distributedOperation':2,
|
||||
'dSAOperation':3,
|
||||
})
|
||||
|
||||
|
||||
class AttributeType(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an AttributeTypeDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID assigned to the attribute type
|
||||
names
|
||||
This list of strings contains all NAMEs of the attribute type
|
||||
desc
|
||||
This string contains description text (DESC) of the attribute type
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the attribute type is marked
|
||||
as OBSOLETE in the schema
|
||||
single_value
|
||||
Integer flag (0 or 1) indicating whether the attribute must
|
||||
have only one value
|
||||
syntax
|
||||
String contains OID of the LDAP syntax assigned to the attribute type
|
||||
no_user_mod
|
||||
Integer flag (0 or 1) indicating whether the attribute is modifiable
|
||||
by a client application
|
||||
equality
|
||||
String contains NAME or OID of the matching rule used for
|
||||
checking whether attribute values are equal
|
||||
substr
|
||||
String contains NAME or OID of the matching rule used for
|
||||
checking whether an attribute value contains another value
|
||||
ordering
|
||||
String contains NAME or OID of the matching rule used for
|
||||
checking whether attribute values are lesser-equal than
|
||||
usage
|
||||
USAGE of an attribute type:
|
||||
0 = userApplications
|
||||
1 = directoryOperation,
|
||||
2 = distributedOperation,
|
||||
3 = dSAOperation
|
||||
sup
|
||||
This list of strings contains NAMEs or OIDs of attribute types
|
||||
this attribute type is derived from
|
||||
"""
|
||||
schema_attribute = u'attributeTypes'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'SUP':(()),
|
||||
'EQUALITY':(None,),
|
||||
'ORDERING':(None,),
|
||||
'SUBSTR':(None,),
|
||||
'SYNTAX':(None,),
|
||||
'SINGLE-VALUE':None,
|
||||
'COLLECTIVE':None,
|
||||
'NO-USER-MODIFICATION':None,
|
||||
'USAGE':('userApplications',),
|
||||
'X-ORIGIN':(None,),
|
||||
'X-ORDERED':(None,),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.sup = d['SUP']
|
||||
self.equality = d['EQUALITY'][0]
|
||||
self.ordering = d['ORDERING'][0]
|
||||
self.substr = d['SUBSTR'][0]
|
||||
self.x_origin = d['X-ORIGIN'][0]
|
||||
self.x_ordered = d['X-ORDERED'][0]
|
||||
try:
|
||||
syntax = d['SYNTAX'][0]
|
||||
except IndexError:
|
||||
self.syntax = None
|
||||
self.syntax_len = None
|
||||
else:
|
||||
if syntax is None:
|
||||
self.syntax = None
|
||||
self.syntax_len = None
|
||||
else:
|
||||
try:
|
||||
self.syntax,syntax_len = d['SYNTAX'][0].split("{")
|
||||
except ValueError:
|
||||
self.syntax = d['SYNTAX'][0]
|
||||
self.syntax_len = None
|
||||
for i in l:
|
||||
if i.startswith("{") and i.endswith("}"):
|
||||
self.syntax_len = int(i[1:-1])
|
||||
else:
|
||||
self.syntax_len = int(syntax_len[:-1])
|
||||
self.single_value = d['SINGLE-VALUE']!=None
|
||||
self.collective = d['COLLECTIVE']!=None
|
||||
self.no_user_mod = d['NO-USER-MODIFICATION']!=None
|
||||
self.usage = AttributeUsage.get(d['USAGE'][0],0)
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append(self.key_list('SUP',self.sup,sep=' $ '))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_attr('EQUALITY',self.equality))
|
||||
result.append(self.key_attr('ORDERING',self.ordering))
|
||||
result.append(self.key_attr('SUBSTR',self.substr))
|
||||
result.append(self.key_attr('SYNTAX',self.syntax))
|
||||
if self.syntax_len!=None:
|
||||
result.append(('{%d}' % (self.syntax_len))*(self.syntax_len>0))
|
||||
result.append({0:'',1:' SINGLE-VALUE'}[self.single_value])
|
||||
result.append({0:'',1:' COLLECTIVE'}[self.collective])
|
||||
result.append({0:'',1:' NO-USER-MODIFICATION'}[self.no_user_mod])
|
||||
result.append(
|
||||
{
|
||||
0:"",
|
||||
1:" USAGE directoryOperation",
|
||||
2:" USAGE distributedOperation",
|
||||
3:" USAGE dSAOperation",
|
||||
}[self.usage]
|
||||
)
|
||||
result.append(self.key_attr('X-ORIGIN',self.x_origin,quoted=1))
|
||||
result.append(self.key_attr('X-ORDERED',self.x_ordered,quoted=1))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class LDAPSyntax(SchemaElement):
|
||||
"""
|
||||
SyntaxDescription
|
||||
|
||||
oid
|
||||
OID assigned to the LDAP syntax
|
||||
desc
|
||||
This string contains description text (DESC) of the LDAP syntax
|
||||
not_human_readable
|
||||
Integer flag (0 or 1) indicating whether the attribute type is marked
|
||||
as not human-readable (X-NOT-HUMAN-READABLE)
|
||||
"""
|
||||
schema_attribute = u'ldapSyntaxes'
|
||||
token_defaults = {
|
||||
'DESC':(None,),
|
||||
'X-NOT-HUMAN-READABLE':(None,),
|
||||
'X-BINARY-TRANSFER-REQUIRED':(None,),
|
||||
'X-SUBST':(None,),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.desc = d['DESC'][0]
|
||||
self.x_subst = d['X-SUBST'][0]
|
||||
self.not_human_readable = \
|
||||
self.oid in NOT_HUMAN_READABLE_LDAP_SYNTAXES or \
|
||||
d['X-NOT-HUMAN-READABLE'][0]=='TRUE'
|
||||
self.x_binary_transfer_required = d['X-BINARY-TRANSFER-REQUIRED'][0]=='TRUE'
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append(self.key_attr('X-SUBST',self.x_subst,quoted=1))
|
||||
result.append(
|
||||
{0:'',1:" X-NOT-HUMAN-READABLE 'TRUE'"}[self.not_human_readable]
|
||||
)
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class MatchingRule(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an MatchingRuleDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID assigned to the matching rule
|
||||
names
|
||||
This list of strings contains all NAMEs of the matching rule
|
||||
desc
|
||||
This string contains description text (DESC) of the matching rule
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the matching rule is marked
|
||||
as OBSOLETE in the schema
|
||||
syntax
|
||||
String contains OID of the LDAP syntax this matching rule is usable with
|
||||
"""
|
||||
schema_attribute = u'matchingRules'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'SYNTAX':(None,),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.syntax = d['SYNTAX'][0]
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_attr('SYNTAX',self.syntax))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class MatchingRuleUse(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an MatchingRuleUseDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID of the accompanying matching rule
|
||||
names
|
||||
This list of strings contains all NAMEs of the matching rule
|
||||
desc
|
||||
This string contains description text (DESC) of the matching rule
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the matching rule is marked
|
||||
as OBSOLETE in the schema
|
||||
applies
|
||||
This list of strings contains NAMEs or OIDs of attribute types
|
||||
for which this matching rule is used
|
||||
"""
|
||||
schema_attribute = u'matchingRuleUse'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'APPLIES':(()),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.applies = d['APPLIES']
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_list('APPLIES',self.applies,sep=' $ '))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class DITContentRule(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an DITContentRuleDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID of the accompanying structural object class
|
||||
names
|
||||
This list of strings contains all NAMEs of the DIT content rule
|
||||
desc
|
||||
This string contains description text (DESC) of the DIT content rule
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the DIT content rule is marked
|
||||
as OBSOLETE in the schema
|
||||
aux
|
||||
This list of strings contains NAMEs or OIDs of all auxiliary
|
||||
object classes usable in an entry of the object class
|
||||
must
|
||||
This list of strings contains NAMEs or OIDs of all attributes
|
||||
an entry of the object class must have which may extend the
|
||||
list of required attributes of the object classes of an entry
|
||||
may
|
||||
This list of strings contains NAMEs or OIDs of additional attributes
|
||||
an entry of the object class may have which may extend the
|
||||
list of optional attributes of the object classes of an entry
|
||||
nots
|
||||
This list of strings contains NAMEs or OIDs of attributes which
|
||||
may not be present in an entry of the object class
|
||||
"""
|
||||
schema_attribute = u'dITContentRules'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'AUX':(()),
|
||||
'MUST':(()),
|
||||
'MAY':(()),
|
||||
'NOT':(()),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.aux = d['AUX']
|
||||
self.must = d['MUST']
|
||||
self.may = d['MAY']
|
||||
self.nots = d['NOT']
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_list('AUX',self.aux,sep=' $ '))
|
||||
result.append(self.key_list('MUST',self.must,sep=' $ '))
|
||||
result.append(self.key_list('MAY',self.may,sep=' $ '))
|
||||
result.append(self.key_list('NOT',self.nots,sep=' $ '))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class DITStructureRule(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an DITStructureRuleDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
ruleid
|
||||
rule ID of the DIT structure rule (only locally unique)
|
||||
names
|
||||
This list of strings contains all NAMEs of the DIT structure rule
|
||||
desc
|
||||
This string contains description text (DESC) of the DIT structure rule
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the DIT content rule is marked
|
||||
as OBSOLETE in the schema
|
||||
form
|
||||
List of strings with NAMEs or OIDs of associated name forms
|
||||
sup
|
||||
List of strings with NAMEs or OIDs of allowed structural object classes
|
||||
of superior entries in the DIT
|
||||
"""
|
||||
schema_attribute = u'dITStructureRules'
|
||||
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'FORM':(None,),
|
||||
'SUP':(()),
|
||||
}
|
||||
|
||||
def set_id(self,element_id):
|
||||
self.ruleid = element_id
|
||||
|
||||
def get_id(self):
|
||||
return self.ruleid
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.form = d['FORM'][0]
|
||||
self.sup = d['SUP']
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.ruleid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_attr('FORM',self.form,quoted=0))
|
||||
result.append(self.key_list('SUP',self.sup,sep=' $ '))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class NameForm(SchemaElement):
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
schema_element_str
|
||||
String containing an NameFormDescription
|
||||
|
||||
Class attributes:
|
||||
|
||||
oid
|
||||
OID of the name form
|
||||
names
|
||||
This list of strings contains all NAMEs of the name form
|
||||
desc
|
||||
This string contains description text (DESC) of the name form
|
||||
obsolete
|
||||
Integer flag (0 or 1) indicating whether the name form is marked
|
||||
as OBSOLETE in the schema
|
||||
form
|
||||
List of strings with NAMEs or OIDs of associated name forms
|
||||
oc
|
||||
String with NAME or OID of structural object classes this name form
|
||||
is usable with
|
||||
must
|
||||
This list of strings contains NAMEs or OIDs of all attributes
|
||||
an RDN must contain
|
||||
may
|
||||
This list of strings contains NAMEs or OIDs of additional attributes
|
||||
an RDN may contain
|
||||
"""
|
||||
schema_attribute = u'nameForms'
|
||||
token_defaults = {
|
||||
'NAME':(()),
|
||||
'DESC':(None,),
|
||||
'OBSOLETE':None,
|
||||
'OC':(None,),
|
||||
'MUST':(()),
|
||||
'MAY':(()),
|
||||
}
|
||||
|
||||
def _set_attrs(self,l,d):
|
||||
self.names = d['NAME']
|
||||
self.desc = d['DESC'][0]
|
||||
self.obsolete = d['OBSOLETE']!=None
|
||||
self.oc = d['OC'][0]
|
||||
self.must = d['MUST']
|
||||
self.may = d['MAY']
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
result = [str(self.oid)]
|
||||
result.append(self.key_list('NAME',self.names,quoted=1))
|
||||
result.append(self.key_attr('DESC',self.desc,quoted=1))
|
||||
result.append({0:'',1:' OBSOLETE'}[self.obsolete])
|
||||
result.append(self.key_attr('OC',self.oc))
|
||||
result.append(self.key_list('MUST',self.must,sep=' $ '))
|
||||
result.append(self.key_list('MAY',self.may,sep=' $ '))
|
||||
return '( %s )' % ''.join(result)
|
||||
|
||||
|
||||
class Entry(IterableUserDict):
|
||||
"""
|
||||
Schema-aware implementation of an LDAP entry class.
|
||||
|
||||
Mainly it holds the attributes in a string-keyed dictionary with
|
||||
the OID as key.
|
||||
"""
|
||||
|
||||
def __init__(self,schema,dn,entry):
|
||||
self._keytuple2attrtype = {}
|
||||
self._attrtype2keytuple = {}
|
||||
self._s = schema
|
||||
self.dn = dn
|
||||
IterableUserDict.IterableUserDict.__init__(self,{})
|
||||
self.update(entry)
|
||||
|
||||
def _at2key(self,nameoroid):
|
||||
"""
|
||||
Return tuple of OID and all sub-types of attribute type specified
|
||||
in nameoroid.
|
||||
"""
|
||||
try:
|
||||
# Mapping already in cache
|
||||
return self._attrtype2keytuple[nameoroid]
|
||||
except KeyError:
|
||||
# Mapping has to be constructed
|
||||
oid = self._s.getoid(ldap.schema.AttributeType,nameoroid)
|
||||
l = nameoroid.lower().split(';')
|
||||
l[0] = oid
|
||||
t = tuple(l)
|
||||
self._attrtype2keytuple[nameoroid] = t
|
||||
return t
|
||||
|
||||
def update(self,dict):
|
||||
for key, value in dict.values():
|
||||
self[key] = value
|
||||
|
||||
def __contains__(self,nameoroid):
|
||||
return self._at2key(nameoroid) in self.data
|
||||
|
||||
def __getitem__(self,nameoroid):
|
||||
return self.data[self._at2key(nameoroid)]
|
||||
|
||||
def __setitem__(self,nameoroid,attr_values):
|
||||
k = self._at2key(nameoroid)
|
||||
self._keytuple2attrtype[k] = nameoroid
|
||||
self.data[k] = attr_values
|
||||
|
||||
def __delitem__(self,nameoroid):
|
||||
k = self._at2key(nameoroid)
|
||||
del self.data[k]
|
||||
del self._attrtype2keytuple[nameoroid]
|
||||
del self._keytuple2attrtype[k]
|
||||
|
||||
def has_key(self,nameoroid):
|
||||
k = self._at2key(nameoroid)
|
||||
return k in self.data
|
||||
|
||||
def keys(self):
|
||||
return self._keytuple2attrtype.values()
|
||||
|
||||
def items(self):
|
||||
return [
|
||||
(k,self[k])
|
||||
for k in self.keys()
|
||||
]
|
||||
|
||||
def attribute_types(
|
||||
self,attr_type_filter=None,raise_keyerror=1
|
||||
):
|
||||
"""
|
||||
Convenience wrapper around SubSchema.attribute_types() which
|
||||
passes object classes of this particular entry as argument to
|
||||
SubSchema.attribute_types()
|
||||
"""
|
||||
return self._s.attribute_types(
|
||||
self.get('objectClass',[]),attr_type_filter,raise_keyerror
|
||||
)
|
498
thesisenv/lib/python3.6/site-packages/ldap/schema/subentry.py
Normal file
498
thesisenv/lib/python3.6/site-packages/ldap/schema/subentry.py
Normal file
@ -0,0 +1,498 @@
|
||||
"""
|
||||
ldap.schema.subentry - subschema subentry handling
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
import ldap.cidict,ldap.schema
|
||||
|
||||
from ldap.compat import urlopen
|
||||
from ldap.schema.models import *
|
||||
|
||||
import ldapurl
|
||||
import ldif
|
||||
|
||||
|
||||
SCHEMA_CLASS_MAPPING = ldap.cidict.cidict()
|
||||
SCHEMA_ATTR_MAPPING = {}
|
||||
|
||||
for o in list(vars().values()):
|
||||
if hasattr(o,'schema_attribute'):
|
||||
SCHEMA_CLASS_MAPPING[o.schema_attribute] = o
|
||||
SCHEMA_ATTR_MAPPING[o] = o.schema_attribute
|
||||
|
||||
SCHEMA_ATTRS = SCHEMA_CLASS_MAPPING.keys()
|
||||
|
||||
|
||||
class SubschemaError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class OIDNotUnique(SubschemaError):
|
||||
|
||||
def __init__(self,desc):
|
||||
self.desc = desc
|
||||
|
||||
def __str__(self):
|
||||
return 'OID not unique for %s' % (self.desc)
|
||||
|
||||
|
||||
class NameNotUnique(SubschemaError):
|
||||
|
||||
def __init__(self,desc):
|
||||
self.desc = desc
|
||||
|
||||
def __str__(self):
|
||||
return 'NAME not unique for %s' % (self.desc)
|
||||
|
||||
|
||||
class SubSchema:
|
||||
"""
|
||||
Arguments:
|
||||
|
||||
sub_schema_sub_entry
|
||||
Dictionary usually returned by LDAP search or the LDIF parser
|
||||
containing the sub schema sub entry
|
||||
|
||||
check_uniqueness
|
||||
Defines whether uniqueness of OIDs and NAME is checked.
|
||||
|
||||
0
|
||||
no check
|
||||
1
|
||||
check but add schema description with work-around
|
||||
2
|
||||
check and raise exception if non-unique OID or NAME is found
|
||||
|
||||
Class attributes:
|
||||
|
||||
sed
|
||||
Dictionary holding the subschema information as pre-parsed
|
||||
SchemaElement objects (do not access directly!)
|
||||
name2oid
|
||||
Dictionary holding the mapping from NAMEs to OIDs
|
||||
(do not access directly!)
|
||||
non_unique_oids
|
||||
List of OIDs used at least twice in the subschema
|
||||
non_unique_names
|
||||
List of NAMEs used at least twice in the subschema for the same schema element
|
||||
"""
|
||||
|
||||
def __init__(self,sub_schema_sub_entry,check_uniqueness=1):
|
||||
|
||||
# Initialize all dictionaries
|
||||
self.name2oid = {}
|
||||
self.sed = {}
|
||||
self.non_unique_oids = {}
|
||||
self.non_unique_names = {}
|
||||
for c in SCHEMA_CLASS_MAPPING.values():
|
||||
self.name2oid[c] = ldap.cidict.cidict()
|
||||
self.sed[c] = {}
|
||||
self.non_unique_names[c] = ldap.cidict.cidict()
|
||||
|
||||
# Transform entry dict to case-insensitive dict
|
||||
e = ldap.cidict.cidict(sub_schema_sub_entry)
|
||||
|
||||
# Build the schema registry in dictionaries
|
||||
for attr_type in SCHEMA_ATTRS:
|
||||
|
||||
for attr_value in filter(None,e.get(attr_type,[])):
|
||||
|
||||
se_class = SCHEMA_CLASS_MAPPING[attr_type]
|
||||
se_instance = se_class(attr_value)
|
||||
se_id = se_instance.get_id()
|
||||
|
||||
if check_uniqueness and se_id in self.sed[se_class]:
|
||||
self.non_unique_oids[se_id] = None
|
||||
if check_uniqueness==1:
|
||||
# Add to subschema by adding suffix to ID
|
||||
suffix_counter = 1
|
||||
new_se_id = se_id
|
||||
while new_se_id in self.sed[se_class]:
|
||||
new_se_id = ';'.join((se_id,str(suffix_counter)))
|
||||
suffix_counter += 1
|
||||
else:
|
||||
se_id = new_se_id
|
||||
elif check_uniqueness>=2:
|
||||
raise OIDNotUnique(attr_value)
|
||||
|
||||
# Store the schema element instance in the central registry
|
||||
self.sed[se_class][se_id] = se_instance
|
||||
|
||||
if hasattr(se_instance,'names'):
|
||||
for name in ldap.cidict.cidict({}.fromkeys(se_instance.names)).keys():
|
||||
if check_uniqueness and name in self.name2oid[se_class]:
|
||||
self.non_unique_names[se_class][se_id] = None
|
||||
raise NameNotUnique(attr_value)
|
||||
else:
|
||||
self.name2oid[se_class][name] = se_id
|
||||
|
||||
# Turn dict into list maybe more handy for applications
|
||||
self.non_unique_oids = self.non_unique_oids.keys()
|
||||
|
||||
return # subSchema.__init__()
|
||||
|
||||
|
||||
def ldap_entry(self):
|
||||
"""
|
||||
Returns a dictionary containing the sub schema sub entry
|
||||
"""
|
||||
# Initialize the dictionary with empty lists
|
||||
entry = {}
|
||||
# Collect the schema elements and store them in
|
||||
# entry's attributes
|
||||
for se_class, elements in self.sed.items():
|
||||
for se in elements.values():
|
||||
se_str = str(se)
|
||||
try:
|
||||
entry[SCHEMA_ATTR_MAPPING[se_class]].append(se_str)
|
||||
except KeyError:
|
||||
entry[SCHEMA_ATTR_MAPPING[se_class]] = [ se_str ]
|
||||
return entry
|
||||
|
||||
def listall(self,schema_element_class,schema_element_filters=None):
|
||||
"""
|
||||
Returns a list of OIDs of all available schema
|
||||
elements of a given schema element class.
|
||||
"""
|
||||
avail_se = self.sed[schema_element_class]
|
||||
if schema_element_filters:
|
||||
result = []
|
||||
for se_key, se in avail_se.items():
|
||||
for fk,fv in schema_element_filters:
|
||||
try:
|
||||
if getattr(se,fk) in fv:
|
||||
result.append(se_key)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
result = avail_se.keys()
|
||||
return result
|
||||
|
||||
|
||||
def tree(self,schema_element_class,schema_element_filters=None):
|
||||
"""
|
||||
Returns a ldap.cidict.cidict dictionary representing the
|
||||
tree structure of the schema elements.
|
||||
"""
|
||||
assert schema_element_class in [ObjectClass,AttributeType]
|
||||
avail_se = self.listall(schema_element_class,schema_element_filters)
|
||||
top_node = '_'
|
||||
tree = ldap.cidict.cidict({top_node:[]})
|
||||
# 1. Pass: Register all nodes
|
||||
for se in avail_se:
|
||||
tree[se] = []
|
||||
# 2. Pass: Register all sup references
|
||||
for se_oid in avail_se:
|
||||
se_obj = self.get_obj(schema_element_class,se_oid,None)
|
||||
if se_obj.__class__!=schema_element_class:
|
||||
# Ignore schema elements not matching schema_element_class.
|
||||
# This helps with falsely assigned OIDs.
|
||||
continue
|
||||
assert se_obj.__class__==schema_element_class, \
|
||||
"Schema element referenced by %s must be of class %s but was %s" % (
|
||||
se_oid,schema_element_class.__name__,se_obj.__class__
|
||||
)
|
||||
for s in se_obj.sup or ('_',):
|
||||
sup_oid = self.getoid(schema_element_class,s)
|
||||
try:
|
||||
tree[sup_oid].append(se_oid)
|
||||
except:
|
||||
pass
|
||||
return tree
|
||||
|
||||
|
||||
def getoid(self,se_class,nameoroid,raise_keyerror=0):
|
||||
"""
|
||||
Get an OID by name or OID
|
||||
"""
|
||||
nameoroid_stripped = nameoroid.split(';')[0].strip()
|
||||
if nameoroid_stripped in self.sed[se_class]:
|
||||
# name_or_oid is already a registered OID
|
||||
return nameoroid_stripped
|
||||
else:
|
||||
try:
|
||||
result_oid = self.name2oid[se_class][nameoroid_stripped]
|
||||
except KeyError:
|
||||
if raise_keyerror:
|
||||
raise KeyError('No registered %s-OID for nameoroid %s' % (se_class.__name__,repr(nameoroid_stripped)))
|
||||
else:
|
||||
result_oid = nameoroid_stripped
|
||||
return result_oid
|
||||
|
||||
|
||||
def get_inheritedattr(self,se_class,nameoroid,name):
|
||||
"""
|
||||
Get a possibly inherited attribute specified by name
|
||||
of a schema element specified by nameoroid.
|
||||
Returns None if class attribute is not set at all.
|
||||
|
||||
Raises KeyError if no schema element is found by nameoroid.
|
||||
"""
|
||||
se = self.sed[se_class][self.getoid(se_class,nameoroid)]
|
||||
try:
|
||||
result = getattr(se,name)
|
||||
except AttributeError:
|
||||
result = None
|
||||
if result is None and se.sup:
|
||||
result = self.get_inheritedattr(se_class,se.sup[0],name)
|
||||
return result
|
||||
|
||||
|
||||
def get_obj(self,se_class,nameoroid,default=None,raise_keyerror=0):
|
||||
"""
|
||||
Get a schema element by name or OID
|
||||
"""
|
||||
se_oid = self.getoid(se_class,nameoroid)
|
||||
try:
|
||||
se_obj = self.sed[se_class][se_oid]
|
||||
except KeyError:
|
||||
if raise_keyerror:
|
||||
raise KeyError('No ldap.schema.%s instance with nameoroid %s and se_oid %s' % (
|
||||
se_class.__name__,repr(nameoroid),repr(se_oid))
|
||||
)
|
||||
else:
|
||||
se_obj = default
|
||||
return se_obj
|
||||
|
||||
|
||||
def get_inheritedobj(self,se_class,nameoroid,inherited=None):
|
||||
"""
|
||||
Get a schema element by name or OID with all class attributes
|
||||
set including inherited class attributes
|
||||
"""
|
||||
inherited = inherited or []
|
||||
se = copy.copy(self.sed[se_class].get(self.getoid(se_class,nameoroid)))
|
||||
if se and hasattr(se,'sup'):
|
||||
for class_attr_name in inherited:
|
||||
setattr(se,class_attr_name,self.get_inheritedattr(se_class,nameoroid,class_attr_name))
|
||||
return se
|
||||
|
||||
|
||||
def get_syntax(self,nameoroid):
|
||||
"""
|
||||
Get the syntax of an attribute type specified by name or OID
|
||||
"""
|
||||
at_oid = self.getoid(AttributeType,nameoroid)
|
||||
try:
|
||||
at_obj = self.get_inheritedobj(AttributeType,at_oid)
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return at_obj.syntax
|
||||
|
||||
|
||||
def get_structural_oc(self,oc_list):
|
||||
"""
|
||||
Returns OID of structural object class in oc_list
|
||||
if any is present. Returns None else.
|
||||
"""
|
||||
# Get tree of all STRUCTURAL object classes
|
||||
oc_tree = self.tree(ObjectClass,[('kind',[0])])
|
||||
# Filter all STRUCTURAL object classes
|
||||
struct_ocs = {}
|
||||
for oc_nameoroid in oc_list:
|
||||
oc_se = self.get_obj(ObjectClass,oc_nameoroid,None)
|
||||
if oc_se and oc_se.kind==0:
|
||||
struct_ocs[oc_se.oid] = None
|
||||
result = None
|
||||
# Build a copy of the oid list, to be cleaned as we go.
|
||||
struct_oc_list = list(struct_ocs)
|
||||
while struct_oc_list:
|
||||
oid = struct_oc_list.pop()
|
||||
for child_oid in oc_tree[oid]:
|
||||
if self.getoid(ObjectClass,child_oid) in struct_ocs:
|
||||
break
|
||||
else:
|
||||
result = oid
|
||||
return result
|
||||
|
||||
|
||||
def get_applicable_aux_classes(self,nameoroid):
|
||||
"""
|
||||
Return a list of the applicable AUXILIARY object classes
|
||||
for a STRUCTURAL object class specified by 'nameoroid'
|
||||
if the object class is governed by a DIT content rule.
|
||||
If there's no DIT content rule all available AUXILIARY
|
||||
object classes are returned.
|
||||
"""
|
||||
content_rule = self.get_obj(DITContentRule,nameoroid)
|
||||
if content_rule:
|
||||
# Return AUXILIARY object classes from DITContentRule instance
|
||||
return content_rule.aux
|
||||
else:
|
||||
# list all AUXILIARY object classes
|
||||
return self.listall(ObjectClass,[('kind',[2])])
|
||||
|
||||
def attribute_types(
|
||||
self,object_class_list,attr_type_filter=None,raise_keyerror=1,ignore_dit_content_rule=0
|
||||
):
|
||||
"""
|
||||
Returns a 2-tuple of all must and may attributes including
|
||||
all inherited attributes of superior object classes
|
||||
by walking up classes along the SUP attribute.
|
||||
|
||||
The attributes are stored in a ldap.cidict.cidict dictionary.
|
||||
|
||||
object_class_list
|
||||
list of strings specifying object class names or OIDs
|
||||
attr_type_filter
|
||||
list of 2-tuples containing lists of class attributes
|
||||
which has to be matched
|
||||
raise_keyerror
|
||||
All KeyError exceptions for non-existent schema elements
|
||||
are ignored
|
||||
ignore_dit_content_rule
|
||||
A DIT content rule governing the structural object class
|
||||
is ignored
|
||||
"""
|
||||
AttributeType = ldap.schema.AttributeType
|
||||
ObjectClass = ldap.schema.ObjectClass
|
||||
|
||||
# Map object_class_list to object_class_oids (list of OIDs)
|
||||
object_class_oids = [
|
||||
self.getoid(ObjectClass,o)
|
||||
for o in object_class_list
|
||||
]
|
||||
# Initialize
|
||||
oid_cache = {}
|
||||
|
||||
r_must,r_may = ldap.cidict.cidict(),ldap.cidict.cidict()
|
||||
if '1.3.6.1.4.1.1466.101.120.111' in object_class_oids:
|
||||
# Object class 'extensibleObject' MAY carry every attribute type
|
||||
for at_obj in self.sed[AttributeType].values():
|
||||
r_may[at_obj.oid] = at_obj
|
||||
|
||||
# Loop over OIDs of all given object classes
|
||||
while object_class_oids:
|
||||
object_class_oid = object_class_oids.pop(0)
|
||||
# Check whether the objectClass with this OID
|
||||
# has already been processed
|
||||
if object_class_oid in oid_cache:
|
||||
continue
|
||||
# Cache this OID as already being processed
|
||||
oid_cache[object_class_oid] = None
|
||||
try:
|
||||
object_class = self.sed[ObjectClass][object_class_oid]
|
||||
except KeyError:
|
||||
if raise_keyerror:
|
||||
raise
|
||||
# Ignore this object class
|
||||
continue
|
||||
assert isinstance(object_class,ObjectClass)
|
||||
assert hasattr(object_class,'must'),ValueError(object_class_oid)
|
||||
assert hasattr(object_class,'may'),ValueError(object_class_oid)
|
||||
for a in object_class.must:
|
||||
se_oid = self.getoid(AttributeType,a,raise_keyerror=raise_keyerror)
|
||||
r_must[se_oid] = self.get_obj(AttributeType,se_oid,raise_keyerror=raise_keyerror)
|
||||
for a in object_class.may:
|
||||
se_oid = self.getoid(AttributeType,a,raise_keyerror=raise_keyerror)
|
||||
r_may[se_oid] = self.get_obj(AttributeType,se_oid,raise_keyerror=raise_keyerror)
|
||||
|
||||
object_class_oids.extend([
|
||||
self.getoid(ObjectClass,o)
|
||||
for o in object_class.sup
|
||||
])
|
||||
|
||||
# Process DIT content rules
|
||||
if not ignore_dit_content_rule:
|
||||
structural_oc = self.get_structural_oc(object_class_list)
|
||||
if structural_oc:
|
||||
# Process applicable DIT content rule
|
||||
try:
|
||||
dit_content_rule = self.get_obj(DITContentRule,structural_oc,raise_keyerror=1)
|
||||
except KeyError:
|
||||
# Not DIT content rule found for structural objectclass
|
||||
pass
|
||||
else:
|
||||
for a in dit_content_rule.must:
|
||||
se_oid = self.getoid(AttributeType,a,raise_keyerror=raise_keyerror)
|
||||
r_must[se_oid] = self.get_obj(AttributeType,se_oid,raise_keyerror=raise_keyerror)
|
||||
for a in dit_content_rule.may:
|
||||
se_oid = self.getoid(AttributeType,a,raise_keyerror=raise_keyerror)
|
||||
r_may[se_oid] = self.get_obj(AttributeType,se_oid,raise_keyerror=raise_keyerror)
|
||||
for a in dit_content_rule.nots:
|
||||
a_oid = self.getoid(AttributeType,a,raise_keyerror=raise_keyerror)
|
||||
try:
|
||||
del r_may[a_oid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Remove all mandantory attribute types from
|
||||
# optional attribute type list
|
||||
for a in list(r_may.keys()):
|
||||
if a in r_must:
|
||||
del r_may[a]
|
||||
|
||||
# Apply attr_type_filter to results
|
||||
if attr_type_filter:
|
||||
for l in [r_must,r_may]:
|
||||
for a in list(l.keys()):
|
||||
for afk,afv in attr_type_filter:
|
||||
try:
|
||||
schema_attr_type = self.sed[AttributeType][a]
|
||||
except KeyError:
|
||||
if raise_keyerror:
|
||||
raise KeyError('No attribute type found in sub schema by name %s' % (a))
|
||||
# If there's no schema element for this attribute type
|
||||
# but still KeyError is to be ignored we filter it away
|
||||
del l[a]
|
||||
break
|
||||
else:
|
||||
if not getattr(schema_attr_type,afk) in afv:
|
||||
del l[a]
|
||||
break
|
||||
|
||||
return r_must,r_may # attribute_types()
|
||||
|
||||
|
||||
def urlfetch(uri,trace_level=0):
|
||||
"""
|
||||
Fetches a parsed schema entry by uri.
|
||||
|
||||
If uri is a LDAP URL the LDAP server is queried directly.
|
||||
Otherwise uri is assumed to point to a LDIF file which
|
||||
is loaded with urllib.
|
||||
"""
|
||||
uri = uri.strip()
|
||||
if uri.startswith(('ldap:', 'ldaps:', 'ldapi:')):
|
||||
ldap_url = ldapurl.LDAPUrl(uri)
|
||||
|
||||
l=ldap.initialize(ldap_url.initializeUrl(),trace_level)
|
||||
l.protocol_version = ldap.VERSION3
|
||||
l.simple_bind_s(ldap_url.who or u'', ldap_url.cred or u'')
|
||||
subschemasubentry_dn = l.search_subschemasubentry_s(ldap_url.dn)
|
||||
if subschemasubentry_dn is None:
|
||||
s_temp = None
|
||||
else:
|
||||
if ldap_url.attrs is None:
|
||||
schema_attrs = SCHEMA_ATTRS
|
||||
else:
|
||||
schema_attrs = ldap_url.attrs
|
||||
s_temp = l.read_subschemasubentry_s(
|
||||
subschemasubentry_dn,attrs=schema_attrs
|
||||
)
|
||||
l.unbind_s()
|
||||
del l
|
||||
else:
|
||||
ldif_file = urlopen(uri)
|
||||
ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1)
|
||||
ldif_parser.parse()
|
||||
subschemasubentry_dn,s_temp = ldif_parser.all_records[0]
|
||||
# Work-around for mixed-cased attribute names
|
||||
subschemasubentry_entry = ldap.cidict.cidict()
|
||||
s_temp = s_temp or {}
|
||||
for at,av in s_temp.items():
|
||||
if at in SCHEMA_CLASS_MAPPING:
|
||||
try:
|
||||
subschemasubentry_entry[at].extend(av)
|
||||
except KeyError:
|
||||
subschemasubentry_entry[at] = av
|
||||
# Finally parse the schema
|
||||
if subschemasubentry_dn!=None:
|
||||
parsed_sub_schema = ldap.schema.SubSchema(subschemasubentry_entry)
|
||||
else:
|
||||
parsed_sub_schema = None
|
||||
return subschemasubentry_dn, parsed_sub_schema
|
@ -0,0 +1,80 @@
|
||||
"""
|
||||
ldap.schema.tokenizer - Low-level parsing functions for schema element strings
|
||||
|
||||
See https://www.python-ldap.org/ for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
TOKENS_FINDALL = re.compile(
|
||||
r"(\()" # opening parenthesis
|
||||
r"|" # or
|
||||
r"(\))" # closing parenthesis
|
||||
r"|" # or
|
||||
r"([^'$()\s]+)" # string of length >= 1 without '$() or whitespace
|
||||
r"|" # or
|
||||
r"('.*?'(?!\w))" # any string or empty string surrounded by single quotes
|
||||
# except if right quote is succeeded by alphanumeric char
|
||||
r"|" # or
|
||||
r"([^\s]+?)", # residue, all non-whitespace strings
|
||||
).findall
|
||||
|
||||
|
||||
def split_tokens(s):
|
||||
"""
|
||||
Returns list of syntax elements with quotes and spaces stripped.
|
||||
"""
|
||||
parts = []
|
||||
parens = 0
|
||||
for opar, cpar, unquoted, quoted, residue in TOKENS_FINDALL(s):
|
||||
if unquoted:
|
||||
parts.append(unquoted)
|
||||
elif quoted:
|
||||
parts.append(quoted[1:-1])
|
||||
elif opar:
|
||||
parens += 1
|
||||
parts.append(opar)
|
||||
elif cpar:
|
||||
parens -= 1
|
||||
parts.append(cpar)
|
||||
elif residue == '$':
|
||||
if not parens:
|
||||
raise ValueError("'$' outside parenthesis in %r" % (s))
|
||||
else:
|
||||
raise ValueError(residue, s)
|
||||
if parens:
|
||||
raise ValueError("Unbalanced parenthesis in %r" % (s))
|
||||
return parts
|
||||
|
||||
def extract_tokens(l,known_tokens):
|
||||
"""
|
||||
Returns dictionary of known tokens with all values
|
||||
"""
|
||||
assert l[0].strip()=="(" and l[-1].strip()==")",ValueError(l)
|
||||
result = {}
|
||||
result.update(known_tokens)
|
||||
i = 0
|
||||
l_len = len(l)
|
||||
while i<l_len:
|
||||
if l[i] in result:
|
||||
token = l[i]
|
||||
i += 1 # Consume token
|
||||
if i<l_len:
|
||||
if l[i] in result:
|
||||
# non-valued
|
||||
result[token] = (())
|
||||
elif l[i]=="(":
|
||||
# multi-valued
|
||||
i += 1 # Consume left parentheses
|
||||
start = i
|
||||
while i<l_len and l[i]!=")":
|
||||
i += 1
|
||||
result[token] = tuple(filter(lambda v:v!='$',l[start:i]))
|
||||
i += 1 # Consume right parentheses
|
||||
else:
|
||||
# single-valued
|
||||
result[token] = l[i],
|
||||
i += 1 # Consume single value
|
||||
else:
|
||||
i += 1 # Consume unrecognized item
|
||||
return result
|
536
thesisenv/lib/python3.6/site-packages/ldap/syncrepl.py
Normal file
536
thesisenv/lib/python3.6/site-packages/ldap/syncrepl.py
Normal file
@ -0,0 +1,536 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ldap.syncrepl - for implementing syncrepl consumer (see RFC 4533)
|
||||
|
||||
See https://www.python-ldap.org/ for project details.
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
# Imports from pyasn1
|
||||
from pyasn1.type import tag, namedtype, namedval, univ, constraint
|
||||
from pyasn1.codec.ber import encoder, decoder
|
||||
|
||||
from ldap.pkginfo import __version__, __author__, __license__
|
||||
from ldap.controls import RequestControl, ResponseControl, KNOWN_RESPONSE_CONTROLS
|
||||
|
||||
__all__ = [
|
||||
'SyncreplConsumer',
|
||||
]
|
||||
|
||||
|
||||
class SyncUUID(univ.OctetString):
|
||||
"""
|
||||
syncUUID ::= OCTET STRING (SIZE(16))
|
||||
"""
|
||||
subtypeSpec = constraint.ValueSizeConstraint(16, 16)
|
||||
|
||||
|
||||
class SyncCookie(univ.OctetString):
|
||||
"""
|
||||
syncCookie ::= OCTET STRING
|
||||
"""
|
||||
|
||||
|
||||
class SyncRequestMode(univ.Enumerated):
|
||||
"""
|
||||
mode ENUMERATED {
|
||||
-- 0 unused
|
||||
refreshOnly (1),
|
||||
-- 2 reserved
|
||||
refreshAndPersist (3)
|
||||
},
|
||||
"""
|
||||
namedValues = namedval.NamedValues(
|
||||
('refreshOnly', 1),
|
||||
('refreshAndPersist', 3)
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1, 3)
|
||||
|
||||
|
||||
class SyncRequestValue(univ.Sequence):
|
||||
"""
|
||||
syncRequestValue ::= SEQUENCE {
|
||||
mode ENUMERATED {
|
||||
-- 0 unused
|
||||
refreshOnly (1),
|
||||
-- 2 reserved
|
||||
refreshAndPersist (3)
|
||||
},
|
||||
cookie syncCookie OPTIONAL,
|
||||
reloadHint BOOLEAN DEFAULT FALSE
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('mode', SyncRequestMode()),
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie()),
|
||||
namedtype.DefaultedNamedType('reloadHint', univ.Boolean(False))
|
||||
)
|
||||
|
||||
|
||||
class SyncRequestControl(RequestControl):
|
||||
"""
|
||||
The Sync Request Control is an LDAP Control [RFC4511] where the
|
||||
controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.1 and the
|
||||
controlValue, an OCTET STRING, contains a BER-encoded
|
||||
syncRequestValue. The criticality field is either TRUE or FALSE.
|
||||
[..]
|
||||
The Sync Request Control is only applicable to the SearchRequest
|
||||
Message.
|
||||
"""
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.1'
|
||||
|
||||
def __init__(self, criticality=1, cookie=None, mode='refreshOnly', reloadHint=False):
|
||||
self.criticality = criticality
|
||||
self.cookie = cookie
|
||||
self.mode = mode
|
||||
self.reloadHint = reloadHint
|
||||
|
||||
def encodeControlValue(self):
|
||||
rcv = SyncRequestValue()
|
||||
rcv.setComponentByName('mode', SyncRequestMode(self.mode))
|
||||
if self.cookie is not None:
|
||||
rcv.setComponentByName('cookie', SyncCookie(self.cookie))
|
||||
if self.reloadHint:
|
||||
rcv.setComponentByName('reloadHint', univ.Boolean(self.reloadHint))
|
||||
return encoder.encode(rcv)
|
||||
|
||||
|
||||
class SyncStateOp(univ.Enumerated):
|
||||
"""
|
||||
state ENUMERATED {
|
||||
present (0),
|
||||
add (1),
|
||||
modify (2),
|
||||
delete (3)
|
||||
},
|
||||
"""
|
||||
namedValues = namedval.NamedValues(
|
||||
('present', 0),
|
||||
('add', 1),
|
||||
('modify', 2),
|
||||
('delete', 3)
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2, 3)
|
||||
|
||||
|
||||
class SyncStateValue(univ.Sequence):
|
||||
"""
|
||||
syncStateValue ::= SEQUENCE {
|
||||
state ENUMERATED {
|
||||
present (0),
|
||||
add (1),
|
||||
modify (2),
|
||||
delete (3)
|
||||
},
|
||||
entryUUID syncUUID,
|
||||
cookie syncCookie OPTIONAL
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('state', SyncStateOp()),
|
||||
namedtype.NamedType('entryUUID', SyncUUID()),
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie())
|
||||
)
|
||||
|
||||
|
||||
class SyncStateControl(ResponseControl):
|
||||
"""
|
||||
The Sync State Control is an LDAP Control [RFC4511] where the
|
||||
controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.2 and the
|
||||
controlValue, an OCTET STRING, contains a BER-encoded SyncStateValue.
|
||||
The criticality is FALSE.
|
||||
[..]
|
||||
The Sync State Control is only applicable to SearchResultEntry and
|
||||
SearchResultReference Messages.
|
||||
"""
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.2'
|
||||
opnames = ('present', 'add', 'modify', 'delete')
|
||||
|
||||
def decodeControlValue(self, encodedControlValue):
|
||||
d = decoder.decode(encodedControlValue, asn1Spec=SyncStateValue())
|
||||
state = d[0].getComponentByName('state')
|
||||
uuid = UUID(bytes=bytes(d[0].getComponentByName('entryUUID')))
|
||||
cookie = d[0].getComponentByName('cookie')
|
||||
if cookie is not None and cookie.hasValue():
|
||||
self.cookie = str(cookie)
|
||||
else:
|
||||
self.cookie = None
|
||||
self.state = self.__class__.opnames[int(state)]
|
||||
self.entryUUID = str(uuid)
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SyncStateControl.controlType] = SyncStateControl
|
||||
|
||||
|
||||
class SyncDoneValue(univ.Sequence):
|
||||
"""
|
||||
syncDoneValue ::= SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDeletes BOOLEAN DEFAULT FALSE
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False))
|
||||
)
|
||||
|
||||
|
||||
class SyncDoneControl(ResponseControl):
|
||||
"""
|
||||
The Sync Done Control is an LDAP Control [RFC4511] where the
|
||||
controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.3 and the
|
||||
controlValue contains a BER-encoded syncDoneValue. The criticality
|
||||
is FALSE (and hence absent).
|
||||
[..]
|
||||
The Sync Done Control is only applicable to the SearchResultDone
|
||||
Message.
|
||||
"""
|
||||
controlType = '1.3.6.1.4.1.4203.1.9.1.3'
|
||||
|
||||
def decodeControlValue(self, encodedControlValue):
|
||||
d = decoder.decode(encodedControlValue, asn1Spec=SyncDoneValue())
|
||||
cookie = d[0].getComponentByName('cookie')
|
||||
if cookie.hasValue():
|
||||
self.cookie = str(cookie)
|
||||
else:
|
||||
self.cookie = None
|
||||
refresh_deletes = d[0].getComponentByName('refreshDeletes')
|
||||
if refresh_deletes.hasValue():
|
||||
self.refreshDeletes = bool(refresh_deletes)
|
||||
else:
|
||||
self.refreshDeletes = None
|
||||
|
||||
KNOWN_RESPONSE_CONTROLS[SyncDoneControl.controlType] = SyncDoneControl
|
||||
|
||||
|
||||
class RefreshDelete(univ.Sequence):
|
||||
"""
|
||||
refreshDelete [1] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDone BOOLEAN DEFAULT TRUE
|
||||
},
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True))
|
||||
)
|
||||
|
||||
|
||||
class RefreshPresent(univ.Sequence):
|
||||
"""
|
||||
refreshPresent [2] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDone BOOLEAN DEFAULT TRUE
|
||||
},
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True))
|
||||
)
|
||||
|
||||
|
||||
class SyncUUIDs(univ.SetOf):
|
||||
"""
|
||||
syncUUIDs SET OF syncUUID
|
||||
"""
|
||||
componentType = SyncUUID()
|
||||
|
||||
|
||||
class SyncIdSet(univ.Sequence):
|
||||
"""
|
||||
syncIdSet [3] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDeletes BOOLEAN DEFAULT FALSE,
|
||||
syncUUIDs SET OF syncUUID
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('cookie', SyncCookie()),
|
||||
namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False)),
|
||||
namedtype.NamedType('syncUUIDs', SyncUUIDs())
|
||||
)
|
||||
|
||||
|
||||
class SyncInfoValue(univ.Choice):
|
||||
"""
|
||||
syncInfoValue ::= CHOICE {
|
||||
newcookie [0] syncCookie,
|
||||
refreshDelete [1] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDone BOOLEAN DEFAULT TRUE
|
||||
},
|
||||
refreshPresent [2] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDone BOOLEAN DEFAULT TRUE
|
||||
},
|
||||
syncIdSet [3] SEQUENCE {
|
||||
cookie syncCookie OPTIONAL,
|
||||
refreshDeletes BOOLEAN DEFAULT FALSE,
|
||||
syncUUIDs SET OF syncUUID
|
||||
}
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType(
|
||||
'newcookie',
|
||||
SyncCookie().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'refreshDelete',
|
||||
RefreshDelete().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'refreshPresent',
|
||||
RefreshPresent().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)
|
||||
)
|
||||
),
|
||||
namedtype.NamedType(
|
||||
'syncIdSet',
|
||||
SyncIdSet().subtype(
|
||||
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SyncInfoMessage:
|
||||
"""
|
||||
The Sync Info Message is an LDAP Intermediate Response Message
|
||||
[RFC4511] where responseName is the object identifier
|
||||
1.3.6.1.4.1.4203.1.9.1.4 and responseValue contains a BER-encoded
|
||||
syncInfoValue. The criticality is FALSE (and hence absent).
|
||||
"""
|
||||
responseName = '1.3.6.1.4.1.4203.1.9.1.4'
|
||||
|
||||
def __init__(self, encodedMessage):
|
||||
d = decoder.decode(encodedMessage, asn1Spec=SyncInfoValue())
|
||||
self.newcookie = None
|
||||
self.refreshDelete = None
|
||||
self.refreshPresent = None
|
||||
self.syncIdSet = None
|
||||
|
||||
for attr in ['newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']:
|
||||
comp = d[0].getComponentByName(attr)
|
||||
|
||||
if comp is not None and comp.hasValue():
|
||||
|
||||
if attr == 'newcookie':
|
||||
self.newcookie = str(comp)
|
||||
return
|
||||
|
||||
val = {}
|
||||
|
||||
cookie = comp.getComponentByName('cookie')
|
||||
if cookie.hasValue():
|
||||
val['cookie'] = str(cookie)
|
||||
|
||||
if attr.startswith('refresh'):
|
||||
val['refreshDone'] = bool(comp.getComponentByName('refreshDone'))
|
||||
elif attr == 'syncIdSet':
|
||||
uuids = []
|
||||
ids = comp.getComponentByName('syncUUIDs')
|
||||
for i in range(len(ids)):
|
||||
uuid = UUID(bytes=bytes(ids.getComponentByPosition(i)))
|
||||
uuids.append(str(uuid))
|
||||
val['syncUUIDs'] = uuids
|
||||
val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes'))
|
||||
|
||||
setattr(self, attr, val)
|
||||
return
|
||||
|
||||
|
||||
class SyncreplConsumer:
|
||||
"""
|
||||
SyncreplConsumer - LDAP syncrepl consumer object.
|
||||
"""
|
||||
|
||||
def syncrepl_search(self, base, scope, mode='refreshOnly', cookie=None, **search_args):
|
||||
"""
|
||||
Starts syncrepl search operation.
|
||||
|
||||
base, scope, and search_args are passed along to
|
||||
self.search_ext unmodified (aside from adding a Sync
|
||||
Request control to any serverctrls provided).
|
||||
|
||||
mode provides syncrepl mode. Can be 'refreshOnly'
|
||||
to finish after synchronization, or
|
||||
'refreshAndPersist' to persist (continue to
|
||||
receive updates) after synchronization.
|
||||
|
||||
cookie: an opaque value representing the replication
|
||||
state of the client. Subclasses should override
|
||||
the syncrepl_set_cookie() and syncrepl_get_cookie()
|
||||
methods to store the cookie appropriately, rather than
|
||||
passing it.
|
||||
|
||||
Only a single syncrepl search may be active on a SyncreplConsumer
|
||||
object. Multiple concurrent syncrepl searches require multiple
|
||||
separate SyncreplConsumer objects and thus multiple connections
|
||||
(LDAPObject instances).
|
||||
"""
|
||||
if cookie is None:
|
||||
cookie = self.syncrepl_get_cookie()
|
||||
|
||||
syncreq = SyncRequestControl(cookie=cookie, mode=mode)
|
||||
|
||||
if 'serverctrls' in search_args:
|
||||
search_args['serverctrls'] += [syncreq]
|
||||
else:
|
||||
search_args['serverctrls'] = [syncreq]
|
||||
|
||||
self.__refreshDone = False
|
||||
return self.search_ext(base, scope, **search_args)
|
||||
|
||||
def syncrepl_poll(self, msgid=-1, timeout=None, all=0):
|
||||
"""
|
||||
polls for and processes responses to the syncrepl_search() operation.
|
||||
Returns False when operation finishes, True if it is in progress, or
|
||||
raises an exception on error.
|
||||
|
||||
If timeout is specified, raises ldap.TIMEOUT in the event of a timeout.
|
||||
|
||||
If all is set to a nonzero value, poll() will return only when finished
|
||||
or when an exception is raised.
|
||||
|
||||
"""
|
||||
while True:
|
||||
type, msg, mid, ctrls, n, v = self.result4(
|
||||
msgid=msgid,
|
||||
timeout=timeout,
|
||||
add_intermediates=1,
|
||||
add_ctrls=1,
|
||||
all=0,
|
||||
)
|
||||
|
||||
if type == 101:
|
||||
# search result. This marks the end of a refreshOnly session.
|
||||
# look for a SyncDone control, save the cookie, and if necessary
|
||||
# delete non-present entries.
|
||||
for c in ctrls:
|
||||
if c.__class__.__name__ != 'SyncDoneControl':
|
||||
continue
|
||||
self.syncrepl_present(None, refreshDeletes=c.refreshDeletes)
|
||||
if c.cookie is not None:
|
||||
self.syncrepl_set_cookie(c.cookie)
|
||||
|
||||
return False
|
||||
|
||||
elif type == 100:
|
||||
# search entry with associated SyncState control
|
||||
for m in msg:
|
||||
dn, attrs, ctrls = m
|
||||
for c in ctrls:
|
||||
if c.__class__.__name__ != 'SyncStateControl':
|
||||
continue
|
||||
if c.state == 'present':
|
||||
self.syncrepl_present([c.entryUUID])
|
||||
elif c.state == 'delete':
|
||||
self.syncrepl_delete([c.entryUUID])
|
||||
else:
|
||||
self.syncrepl_entry(dn, attrs, c.entryUUID)
|
||||
if self.__refreshDone is False:
|
||||
self.syncrepl_present([c.entryUUID])
|
||||
if c.cookie is not None:
|
||||
self.syncrepl_set_cookie(c.cookie)
|
||||
break
|
||||
|
||||
elif type == 121:
|
||||
# Intermediate message. If it is a SyncInfoMessage, parse it
|
||||
for m in msg:
|
||||
rname, resp, ctrls = m
|
||||
if rname != SyncInfoMessage.responseName:
|
||||
continue
|
||||
sim = SyncInfoMessage(resp)
|
||||
if sim.newcookie is not None:
|
||||
self.syncrepl_set_cookie(sim.newcookie)
|
||||
elif sim.refreshPresent is not None:
|
||||
self.syncrepl_present(None, refreshDeletes=False)
|
||||
if 'cookie' in sim.refreshPresent:
|
||||
self.syncrepl_set_cookie(sim.refreshPresent['cookie'])
|
||||
if sim.refreshPresent['refreshDone']:
|
||||
self.__refreshDone = True
|
||||
self.syncrepl_refreshdone()
|
||||
elif sim.refreshDelete is not None:
|
||||
self.syncrepl_present(None, refreshDeletes=True)
|
||||
if 'cookie' in sim.refreshDelete:
|
||||
self.syncrepl_set_cookie(sim.refreshDelete['cookie'])
|
||||
if sim.refreshDelete['refreshDone']:
|
||||
self.__refreshDone = True
|
||||
self.syncrepl_refreshdone()
|
||||
elif sim.syncIdSet is not None:
|
||||
if sim.syncIdSet['refreshDeletes'] is True:
|
||||
self.syncrepl_delete(sim.syncIdSet['syncUUIDs'])
|
||||
else:
|
||||
self.syncrepl_present(sim.syncIdSet['syncUUIDs'])
|
||||
if 'cookie' in sim.syncIdSet:
|
||||
self.syncrepl_set_cookie(sim.syncIdSet['cookie'])
|
||||
|
||||
if all == 0:
|
||||
return True
|
||||
|
||||
|
||||
# virtual methods -- subclass must override these to do useful work
|
||||
|
||||
def syncrepl_set_cookie(self, cookie):
|
||||
"""
|
||||
Called by syncrepl_poll() to store a new cookie provided by the server.
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_get_cookie(self):
|
||||
"""
|
||||
Called by syncrepl_search() to retrieve the cookie stored by syncrepl_set_cookie()
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_present(self, uuids, refreshDeletes=False):
|
||||
"""
|
||||
Called by syncrepl_poll() whenever entry UUIDs are presented to the client.
|
||||
syncrepl_present() is given a list of entry UUIDs (uuids) and a flag
|
||||
(refreshDeletes) which indicates whether the server explicitly deleted
|
||||
non-present entries during the refresh operation.
|
||||
|
||||
If called with a list of uuids, the syncrepl_present() implementation
|
||||
should record those uuids as present in the directory.
|
||||
|
||||
If called with uuids set to None and refreshDeletes set to False,
|
||||
syncrepl_present() should delete all non-present entries from the local
|
||||
mirror, and reset the list of recorded uuids.
|
||||
|
||||
If called with uuids set to None and refreshDeletes set to True,
|
||||
syncrepl_present() should reset the list of recorded uuids, without
|
||||
deleting any entries.
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_delete(self, uuids):
|
||||
"""
|
||||
Called by syncrepl_poll() to delete entries. A list
|
||||
of UUIDs of the entries to be deleted is given in the
|
||||
uuids parameter.
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_entry(self, dn, attrs, uuid):
|
||||
"""
|
||||
Called by syncrepl_poll() for any added or modified entries.
|
||||
|
||||
The provided uuid is used to identify the provided entry in
|
||||
any future modification (including dn modification), deletion,
|
||||
and presentation operations.
|
||||
"""
|
||||
pass
|
||||
|
||||
def syncrepl_refreshdone(self):
|
||||
"""
|
||||
Called by syncrepl_poll() between refresh and persist phase.
|
||||
|
||||
It indicates that initial synchronization is done and persist phase
|
||||
follows.
|
||||
"""
|
||||
pass
|
@ -0,0 +1 @@
|
||||
pip
|
@ -0,0 +1,149 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: ldap3
|
||||
Version: 2.5
|
||||
Summary: A strictly RFC 4510 conforming LDAP V3 pure Python client library
|
||||
Home-page: https://github.com/cannatag/ldap3
|
||||
Author: Giovanni Cannata
|
||||
Author-email: cannatag@gmail.com
|
||||
License: LGPL v3
|
||||
Keywords: python3 python2 ldap
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
|
||||
Requires-Dist: pyasn1 (>=0.1.8)
|
||||
|
||||
LDAP3
|
||||
=====
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/ldap3.svg
|
||||
:target: https://pypi.python.org/pypi/ldap3/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/ldap3.svg
|
||||
:target: https://pypi.python.org/pypi/ldap3/
|
||||
:alt: License
|
||||
|
||||
.. image:: https://img.shields.io/travis/cannatag/ldap3/master.svg
|
||||
:target: https://travis-ci.org/cannatag/ldap3
|
||||
:alt: TRAVIS-CI build status for master branch
|
||||
|
||||
|
||||
ldap3 is a strictly RFC 4510 conforming **LDAP V3 pure Python client** library. The same codebase runs in Python 2, Python 3, PyPy and PyPy3.
|
||||
|
||||
|
||||
Version 2 warning
|
||||
-----------------
|
||||
|
||||
In version 2 of ldap3 some default values have been changed and the ldap3 namespace has been decluttered, removing redundant
|
||||
constants (look at the changelog for details). Also, the result code constants were moved to ldap3.core.results and the ldap3 custom exceptions
|
||||
were stored in ldap3.core.exceptions. If you experience errors in your existing code you should rearrange the import statements or explicitly
|
||||
set the defaults to their former values.
|
||||
|
||||
|
||||
A more pythonic LDAP
|
||||
--------------------
|
||||
|
||||
LDAP operations look clumsy and hard-to-use because they reflect the old-age idea that time-consuming operations should be performed client-side
|
||||
to not hog the server with heavy elaborations. To alleviate this ldap3 includes a fully functional **Abstraction Layer** that lets you
|
||||
interact with the LDAP server in a modern and *pythonic* way. With the Abstraction Layer you don't need to directly issue any LDAP operation at all.
|
||||
|
||||
|
||||
Home Page
|
||||
---------
|
||||
|
||||
Project home page is https://github.com/cannatag/ldap3
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Documentation is available at http://ldap3.readthedocs.io
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The ldap3 project is open source software released under the **LGPL v3 license**.
|
||||
Copyright 2013 - 2018 Giovanni Cannata
|
||||
|
||||
|
||||
PEP8 Compliance
|
||||
---------------
|
||||
|
||||
ldap3 is PEP8 compliant, except for line length.
|
||||
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
Package download is available at https://pypi.python.org/pypi/ldap3.
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Install with **pip install ldap3**
|
||||
|
||||
|
||||
Git repository
|
||||
--------------
|
||||
|
||||
You can download the latest source at https://github.com/cannatag/ldap3
|
||||
|
||||
|
||||
Continuous integration
|
||||
----------------------
|
||||
|
||||
Continuous integration for testing is at https://travis-ci.org/cannatag/ldap3
|
||||
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
You can submit support tickets on https://github.com/cannatag/ldap3/issues/new
|
||||
You can submit pull request on the **dev** branch at https://github.com/cannatag/ldap3/tree/dev
|
||||
|
||||
|
||||
Thanks to
|
||||
---------
|
||||
|
||||
* **Ilya Etingof**, the author of the *pyasn1* package for his excellent work and support.
|
||||
|
||||
* **Mark Lutz** for his *Learning Python* and *Programming Python* excellent books series and **John Goerzen** and **Brandon Rhodes** for their book *Foundations of Python Network Programming*. These books are wonderful tools for learning Python and this project owes a lot to them.
|
||||
|
||||
* **JetBrains** for donating to this project the Open Source license of *PyCharm Professional*.
|
||||
|
||||
* **GitHub** for providing the *free source repository space and the tools* I use to develop this project.
|
||||
|
||||
* The **FreeIPA** team for letting me use their demo LDAP server in the ldap3 tutorial.
|
||||
|
||||
|
||||
Contact me
|
||||
----------
|
||||
|
||||
For information and suggestions you can contact me at cannatag@gmail.com. You can also open a support ticket on https://github.com/cannatag/ldap3/issues/new
|
||||
|
||||
|
||||
Donate
|
||||
------
|
||||
|
||||
If you want to keep this project up and running you can send me an Amazon gift card. I will use it to improve my skills in the Information and Communication technology.
|
||||
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
Updated changelog at https://ldap3.readthedocs.io/changelog.html
|
||||
|
||||
|
||||
|
215
thesisenv/lib/python3.6/site-packages/ldap3-2.5.dist-info/RECORD
Normal file
215
thesisenv/lib/python3.6/site-packages/ldap3-2.5.dist-info/RECORD
Normal file
@ -0,0 +1,215 @@
|
||||
ldap3/__init__.py,sha256=y8Bwq-3LFZvNnq_8B9IsGWFCG4AK2t08lGHWfTqgXP0,4129
|
||||
ldap3/version.py,sha256=ommyVm3ssFqU9cXOvdU1_GEOR580YlL3-sW0yvWTPBY,684
|
||||
ldap3/abstract/__init__.py,sha256=SjLzFchn_GXzh8piUYQElcRHCg3sUBRr5qB0me7w6d8,2166
|
||||
ldap3/abstract/attrDef.py,sha256=_KnBfzdDtI4HXLGXcffRx5Ca1wsYqwC_-ylDDoso1qc,4983
|
||||
ldap3/abstract/attribute.py,sha256=sMghD84wu-JvX-CNRcXGSzKnCfNN_q5jkH1mnOs3QOY,12598
|
||||
ldap3/abstract/cursor.py,sha256=oi4g2ExXySrcDN3i-JUNRB6IjTYCxGNta61iRiS5Dig,44095
|
||||
ldap3/abstract/entry.py,sha256=rfoMOvDGjCvvDi6V-W5RmzLs4PoHw9PwOOBGd9BwdBo,34312
|
||||
ldap3/abstract/objectDef.py,sha256=MBUd0W3GjeuKLRtW39oiY3VbZ6LiYLzJNVMqQGTKxws,11808
|
||||
ldap3/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/core/connection.py,sha256=HoR68BgpqMVRswcgf28GQgWCy3i99JFxobPFDTYX4tk,77348
|
||||
ldap3/core/exceptions.py,sha256=1Thc7Am0KwRgmxDYNiBVMjjFeaOfTOy4Pc5if2C-Izc,17059
|
||||
ldap3/core/pooling.py,sha256=S43y_B23EFJT3O56XUTH3z1qY_Msoor2xGuD-eEMaOw,14131
|
||||
ldap3/core/results.py,sha256=GORuTTO7jIenzNO_8xht4jz6ya1tcMWkwRky6yV4Pqg,5508
|
||||
ldap3/core/server.py,sha256=WgO629ZOFhJW9ZK8TXk8I_5kiwYOrTWgxymwGdYx2jE,28554
|
||||
ldap3/core/timezone.py,sha256=Q2juioasABK3X5x2kJNxNfBxbqMx0S7EG6n0JR3qjPA,1620
|
||||
ldap3/core/tls.py,sha256=QhVwawW2MBpDE0hYYT6FY7kMzH5Hvttb3xOtoWY3tMs,15497
|
||||
ldap3/core/usage.py,sha256=6ZR81aSmt5xsF5f5ychtWyR5ByFcOkpFnm2MWAFnpcY,10690
|
||||
ldap3/extend/__init__.py,sha256=mMQcu3Bcuh3uVZ0xdDo4vWsEKGfkYPY2vLKeD3Hq53w,12677
|
||||
ldap3/extend/operation.py,sha256=OMg9Cfg6CRhSQ-H6zuZ0U-twaQYUWt6dzcq8ja11yUg,3734
|
||||
ldap3/extend/microsoft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/extend/microsoft/addMembersToGroups.py,sha256=DnQe2PTuJ7jzIHGo06qDYOtut8PRyoitJoKX5XvqeUs,3392
|
||||
ldap3/extend/microsoft/dirSync.py,sha256=K7ZqGhn0xX9rvbnxvwAwhvHlKDQ_gHlSweZuStnzhPw,4108
|
||||
ldap3/extend/microsoft/modifyPassword.py,sha256=KwI49Pv3tX2Bc1BzDKMb7VVtE5m2mMEk9rNJ27kob1s,3058
|
||||
ldap3/extend/microsoft/removeMembersFromGroups.py,sha256=hUeE5wQE9O-vZ0107A0iTPxF-fg7Y1K9bthHAvuzNYE,3702
|
||||
ldap3/extend/microsoft/unlockAccount.py,sha256=rM9JEZSk4xiL6KBHzVLmaBrHnwgLAX8gDyNN1cuaJeY,2104
|
||||
ldap3/extend/novell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/extend/novell/addMembersToGroups.py,sha256=f7tH7wjnheJlJ0C24hhLnF9rWICPxBUwIer5KCUgC9o,7593
|
||||
ldap3/extend/novell/checkGroupsMemberships.py,sha256=kVCUzR2nLsJ2Oj1HKv7XUKU9mIVeEBUZIZTcPrRILvM,7730
|
||||
ldap3/extend/novell/endTransaction.py,sha256=XQx8OXHfe7c69_Gy6J_B1BbPsd6xDKfreek2ZwjrOd4,2252
|
||||
ldap3/extend/novell/getBindDn.py,sha256=fZodgFsb4StlbQqVew0hxgxI2Lq2rrgPHDUlvx8oo2o,1422
|
||||
ldap3/extend/novell/listReplicas.py,sha256=9J57u02qZb1dWYLQoTLyysAQ3v-LqQrSYtGCc5ipnqo,1856
|
||||
ldap3/extend/novell/nmasGetUniversalPassword.py,sha256=_wtmWazGkVGPMNyq3K03CtYMFwhcRD8StrYVsxhFojs,2225
|
||||
ldap3/extend/novell/nmasSetUniversalPassword.py,sha256=tj27EA9ureYZypk8J8RXt6lIpWavE7B68DtQQewA7_I,2077
|
||||
ldap3/extend/novell/partition_entry_count.py,sha256=3MPDjYelnufnS-Z8GNnJQwAcIRR8jqx5jWs2wTCe51I,2077
|
||||
ldap3/extend/novell/removeMembersFromGroups.py,sha256=IY1lZROZv6h9iq_SajAnhhl7lQdOAW-2fq7cKHIX5AQ,7683
|
||||
ldap3/extend/novell/replicaInfo.py,sha256=FqloA0AAYldUir2qBWYuWd5IkhljeTAD9upAOcg0Ma0,3391
|
||||
ldap3/extend/novell/startTransaction.py,sha256=8aCHtIoVm1a6T9gT3JVHdQTQaNzWlI4zJwy1RuQpWgU,2293
|
||||
ldap3/extend/standard/PagedSearch.py,sha256=xAiDnnnPsSGuiYyR75K135vPTGE8xciMbVxel2ZaUZI,5262
|
||||
ldap3/extend/standard/PersistentSearch.py,sha256=__5zYtYCjK-BahCoKcVE4K5aRsAkJa72NEoJlvwIxzQ,4832
|
||||
ldap3/extend/standard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/extend/standard/modifyPassword.py,sha256=2AW-kLW6x0d2kRLxC12-U9tAkr7sBaPBI1oOSkM53Lg,3516
|
||||
ldap3/extend/standard/whoAmI.py,sha256=DVz_CBR_Aa5wReFENbi25Jwa1W8drkOP01dvB9N2OZY,1389
|
||||
ldap3/operation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/operation/abandon.py,sha256=KbZZh8mEiLiI74pzmsoAGgdXOAYwSAjkzYqO7qUBS_0,1139
|
||||
ldap3/operation/add.py,sha256=yf2Vk8DXPCgmUvZsOGzDTP2n5DZdN27jv-m2VebAa1w,2983
|
||||
ldap3/operation/bind.py,sha256=zL2WvoGNxPFc_N5vaFsXbCrUPc9_lN-YiAic1fYSp3I,7722
|
||||
ldap3/operation/compare.py,sha256=wb95kHzCSW2yzyqKZq069KOp4Z9HJPlXbL4-lQKwFZY,2467
|
||||
ldap3/operation/delete.py,sha256=39KVqto4SN1RfTPn5sviQbRshMegkRMtmO8TjwtgE-k,1527
|
||||
ldap3/operation/extended.py,sha256=iiSk3rJc9StRDH4MlWUsqYWDJ8AEyjVIobnrsgAHWIM,4861
|
||||
ldap3/operation/modify.py,sha256=lorJRAkpYJ7eUjUUlrY75ViIKRby0CvEh2TO9mD0nmg,3927
|
||||
ldap3/operation/modifyDn.py,sha256=wUYCLQHY9FQH_Ez_Zr6DVto_GYbp2VaXMR3Nf-NQkQE,2358
|
||||
ldap3/operation/search.py,sha256=6fjlO7YrLgR6xmxPJ9aQIBrxoaPy6-wtL17rtGCOFzU,28048
|
||||
ldap3/operation/unbind.py,sha256=H5vJQONvIaeHG9hTVSdeHpH6JiYzBQ_WHndAGDlPaFo,1012
|
||||
ldap3/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/protocol/controls.py,sha256=rX5fh32rXRB8WB7iWUFdxGo2D1UEJRBGcQonpREDO2I,1392
|
||||
ldap3/protocol/convert.py,sha256=I0Wii14CeUXFMvezitGOkHqUlB4gYUMa34nubhqXfKU,9456
|
||||
ldap3/protocol/microsoft.py,sha256=FYmfIbxRUsmh6acWo8rr3JHdjJquemW4l6p5cEiWXtk,5429
|
||||
ldap3/protocol/novell.py,sha256=UvKadtYTaFWyTZg4j5NYA9NY-kNNbndNI3wiTpDtTQk,5157
|
||||
ldap3/protocol/oid.py,sha256=3ZAxWr36Uv-KTQN56nPzVSTnOq0v6T-qkPYRW7UgFhQ,127654
|
||||
ldap3/protocol/persistentSearch.py,sha256=F-po7N8e1Fx5H7EPSCm6-a6RGlMO4S-p3A7JFzscUQk,3177
|
||||
ldap3/protocol/rfc2696.py,sha256=92n3hvSWbS0l5t6uJmQBC04nx3d6IixOk35_O8kcU1c,2267
|
||||
ldap3/protocol/rfc2849.py,sha256=Mh_s5A89okrpX1mJbgFYV2dRGlleXRhfbjwKxI5LhUk,10477
|
||||
ldap3/protocol/rfc3062.py,sha256=agvRijIdem8WNQO7C_nViuDCp1j2J3E-Cr_u2S-rC4k,2955
|
||||
ldap3/protocol/rfc4511.py,sha256=wwUqQdQVRM3ffojGWzvqS3-5z4ARThl0o54Gnv3JotQ,42545
|
||||
ldap3/protocol/rfc4512.py,sha256=jv0J4HQUijFPI8bZREoGJqFv3Albbu6ppkJIk-_xjFo,38737
|
||||
ldap3/protocol/rfc4527.py,sha256=6uIuSzw2dNkJaCtXw3W0axUOVhnaw94yaCmIG4s21Uc,2078
|
||||
ldap3/protocol/formatters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/protocol/formatters/formatters.py,sha256=25HckFVfyqb8cb7TqYQkCT5EXdDgn6pGl7_HWeyVkoQ,15162
|
||||
ldap3/protocol/formatters/standard.py,sha256=7Hlv3Lypt9Q1a6Y5mDx7hGrOJvaUN6NPGiTSxx4qQaI,14954
|
||||
ldap3/protocol/formatters/validators.py,sha256=0B9UQXTT8Zj7IDmHQfATmxsJbhg1fIInKrnIRkGOGCE,14704
|
||||
ldap3/protocol/sasl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/protocol/sasl/digestMd5.py,sha256=01nkAj3MfL-NRvOoo2qmDS7v-hh4s8771IMkBu_3dx8,5382
|
||||
ldap3/protocol/sasl/external.py,sha256=0L_Gqc6FcL9KNFYcgWjuHeXubgISvfKgK3hzm2v3mAA,1051
|
||||
ldap3/protocol/sasl/kerberos.py,sha256=EtbW5Z_WA1i031dN8iYTfNTUuV8KocHANS4DRiehRr4,5038
|
||||
ldap3/protocol/sasl/plain.py,sha256=1jTwPbkmqtLMzG2uxuX1WBWR25DMM_MOxY6m-qFxkwU,2235
|
||||
ldap3/protocol/sasl/sasl.py,sha256=0NxB_y1m24HMTsmPTXL3-EVUfjjcPu2KZ4dS9QKFmZM,7309
|
||||
ldap3/protocol/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/protocol/schemas/ad2012R2.py,sha256=0OKiRtDlt7eMuDtLb-2BLmZcOUmJ-X0kgaZuxDLvU9o,333672
|
||||
ldap3/protocol/schemas/ds389.py,sha256=pJvqOKjZpD12fNtxdOylwHDzRvwNLlvqta6tsx6FbHU,310500
|
||||
ldap3/protocol/schemas/edir888.py,sha256=ZZv8LFCK-5a-Xy-X6nNktTCbiMtyq29mWBHgNWpu6Ek,178049
|
||||
ldap3/protocol/schemas/slapd24.py,sha256=xzTijWlh2ClIqt2YiCY9WNA7ewMD9bqhH0OG0OkVSHU,129245
|
||||
ldap3/strategy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/strategy/asyncStream.py,sha256=rR-QwtFZubh_1oM9p_Zc9HtP-Cqp-8XNupsTynAHSz0,4460
|
||||
ldap3/strategy/asynchronous.py,sha256=CkfnrzjEpHv8jxQ5B9uINgy4J36nXSnE5hcLDW3HntI,10715
|
||||
ldap3/strategy/base.py,sha256=9NmLXgTJ6pV-oQwU4iZg0NSs5RZQ6CM7u38b6u2H8_o,48078
|
||||
ldap3/strategy/ldifProducer.py,sha256=AV7PPwXUWuPYFf0TYFT2fJTFhu2CTXsjb1aZ7NN8sxw,5702
|
||||
ldap3/strategy/mockAsync.py,sha256=rjSjGUm6pHCzvRwafDfcNwPFjROTYi0kgp8cUgfNSCc,10259
|
||||
ldap3/strategy/mockBase.py,sha256=hA9GG0UOAoDWOAfHbq49xhAF9v2dS0Gjn4SHsANvgkE,44889
|
||||
ldap3/strategy/mockSync.py,sha256=2xRerRhLnABNN7M7EvcsgDsfMnZn5897Vgvel2RGFM8,7315
|
||||
ldap3/strategy/restartable.py,sha256=R5Hd8YJt_lUPkIi5JGezOnNsMzJRCwdcLf6Uu-vlnr0,12930
|
||||
ldap3/strategy/reusable.py,sha256=J9DOpiWaxe6iGpf0vCHCK_k_L6AaL_1VqHNp-3T8E1A,25232
|
||||
ldap3/strategy/sync.py,sha256=4_4ilTCBCl8MOPsKxADxr3i98SpnEujedogKD29dPdQ,10782
|
||||
ldap3/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
ldap3/utils/asn1.py,sha256=kST57JjX7ZJ6InWv73vjsBKlgRpgSu_dCCc4suhwxsU,9304
|
||||
ldap3/utils/ciDict.py,sha256=OfmgdAQeBahpdtceRMWqGkfS__JU1I5KdOhUzSHvtBA,7400
|
||||
ldap3/utils/config.py,sha256=7gEkg7rO8FLR7xPS-CjySlFBMqOQ8XcCOEmkFEE5Nm4,13370
|
||||
ldap3/utils/conv.py,sha256=TQnBDEZ8cuhQ-u9TIlPkYvTc_XkBXaWpFhqr55VvQmU,8267
|
||||
ldap3/utils/dn.py,sha256=rgvNoKTOw1befL2uiU6NtQocJEJiZlXQ9q6qypvHSUM,13258
|
||||
ldap3/utils/hashed.py,sha256=QegyN9OkmpG6u5ah968dshWLRCyixGeab6H0vs7dVHc,3575
|
||||
ldap3/utils/log.py,sha256=PdJx5qNyS3Uihg77cdIh69LUYYmmUkE0TnxgJVy9crw,6738
|
||||
ldap3/utils/ntlm.py,sha256=YAlYH2VQxLEVv4U9YeWwcwNDE_6Ts2aplklNc8wkqIQ,19719
|
||||
ldap3/utils/ordDict.py,sha256=mmMzSklrAxwRZprA1Lj5K1D-eD-HLWVHxQVQD0NiPnQ,4251
|
||||
ldap3/utils/repr.py,sha256=F5zOv9mcI27U_kOIYAG-1YnQZ7M7UMckRpcFOMB07S4,1700
|
||||
ldap3/utils/tls_backport.py,sha256=-r1PvYoVUVbcqtjnK-O4jWbY4JEt4a7Mp5EO9q0Gkpc,5426
|
||||
ldap3/utils/uri.py,sha256=wjwCiFNE5-FKxaaofYUUT8wOu43zeB-9FWCDrKTYF3Y,4900
|
||||
ldap3-2.5.dist-info/METADATA,sha256=W_XYqIDOTbYKkzcfrRIOjkOZpymH7DXcEQF_njAWkQE,4662
|
||||
ldap3-2.5.dist-info/RECORD,,
|
||||
ldap3-2.5.dist-info/WHEEL,sha256=saUSQBLOUjf5ACZdNkhQ0lB6XrHU-l4vpzxq_W1n_AY,116
|
||||
ldap3-2.5.dist-info/top_level.txt,sha256=Zg1GRSTgLedl2RfLDLI0W0OaUFdYc0H1zzRbrK96JBw,6
|
||||
ldap3-2.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
ldap3/abstract/__pycache__/attribute.cpython-36.pyc,,
|
||||
ldap3/abstract/__pycache__/entry.cpython-36.pyc,,
|
||||
ldap3/abstract/__pycache__/objectDef.cpython-36.pyc,,
|
||||
ldap3/abstract/__pycache__/cursor.cpython-36.pyc,,
|
||||
ldap3/abstract/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/abstract/__pycache__/attrDef.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/modify.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/delete.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/unbind.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/bind.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/compare.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/search.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/abandon.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/extended.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/modifyDn.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/operation/__pycache__/add.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/getBindDn.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/removeMembersFromGroups.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/replicaInfo.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/nmasSetUniversalPassword.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/addMembersToGroups.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/endTransaction.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/startTransaction.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/checkGroupsMemberships.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/partition_entry_count.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/nmasGetUniversalPassword.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/extend/novell/__pycache__/listReplicas.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/removeMembersFromGroups.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/addMembersToGroups.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/modifyPassword.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/dirSync.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/unlockAccount.cpython-36.pyc,,
|
||||
ldap3/extend/microsoft/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/extend/__pycache__/operation.cpython-36.pyc,,
|
||||
ldap3/extend/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/extend/standard/__pycache__/PagedSearch.cpython-36.pyc,,
|
||||
ldap3/extend/standard/__pycache__/whoAmI.cpython-36.pyc,,
|
||||
ldap3/extend/standard/__pycache__/modifyPassword.cpython-36.pyc,,
|
||||
ldap3/extend/standard/__pycache__/PersistentSearch.cpython-36.pyc,,
|
||||
ldap3/extend/standard/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/exceptions.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/results.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/usage.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/tls.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/pooling.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/connection.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/server.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/core/__pycache__/timezone.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/conv.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/repr.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/uri.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/dn.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/config.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/ciDict.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/asn1.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/log.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/hashed.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/tls_backport.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/ntlm.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/utils/__pycache__/ordDict.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/sasl.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/external.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/plain.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/digestMd5.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/kerberos.cpython-36.pyc,,
|
||||
ldap3/protocol/sasl/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/protocol/formatters/__pycache__/formatters.cpython-36.pyc,,
|
||||
ldap3/protocol/formatters/__pycache__/validators.cpython-36.pyc,,
|
||||
ldap3/protocol/formatters/__pycache__/standard.cpython-36.pyc,,
|
||||
ldap3/protocol/formatters/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/novell.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc4512.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc3062.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/oid.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc2849.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc4511.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/convert.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/controls.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/microsoft.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/persistentSearch.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc4527.cpython-36.pyc,,
|
||||
ldap3/protocol/__pycache__/rfc2696.cpython-36.pyc,,
|
||||
ldap3/protocol/schemas/__pycache__/ds389.cpython-36.pyc,,
|
||||
ldap3/protocol/schemas/__pycache__/ad2012R2.cpython-36.pyc,,
|
||||
ldap3/protocol/schemas/__pycache__/slapd24.cpython-36.pyc,,
|
||||
ldap3/protocol/schemas/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/protocol/schemas/__pycache__/edir888.cpython-36.pyc,,
|
||||
ldap3/__pycache__/version.cpython-36.pyc,,
|
||||
ldap3/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/mockSync.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/asynchronous.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/restartable.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/ldifProducer.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/asyncStream.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/mockBase.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/mockAsync.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/base.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/reusable.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/__init__.cpython-36.pyc,,
|
||||
ldap3/strategy/__pycache__/sync.cpython-36.pyc,,
|
@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.31.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -0,0 +1 @@
|
||||
ldap3
|
145
thesisenv/lib/python3.6/site-packages/ldap3/__init__.py
Normal file
145
thesisenv/lib/python3.6/site-packages/ldap3/__init__.py
Normal file
@ -0,0 +1,145 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2013.05.15
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from types import GeneratorType
|
||||
|
||||
# authentication
|
||||
ANONYMOUS = 'ANONYMOUS'
|
||||
SIMPLE = 'SIMPLE'
|
||||
SASL = 'SASL'
|
||||
NTLM = 'NTLM'
|
||||
|
||||
# SASL MECHANISMS
|
||||
EXTERNAL = 'EXTERNAL'
|
||||
DIGEST_MD5 = 'DIGEST-MD5'
|
||||
KERBEROS = GSSAPI = 'GSSAPI'
|
||||
PLAIN = 'PLAIN'
|
||||
|
||||
AUTO_BIND_NONE = 'NONE' # same as False
|
||||
AUTO_BIND_NO_TLS = 'NO_TLS' # same as True
|
||||
AUTO_BIND_TLS_BEFORE_BIND = 'TLS_BEFORE_BIND'
|
||||
AUTO_BIND_TLS_AFTER_BIND = 'TLS_AFTER_BIND'
|
||||
|
||||
# server IP dual stack mode
|
||||
IP_SYSTEM_DEFAULT = 'IP_SYSTEM_DEFAULT'
|
||||
IP_V4_ONLY = 'IP_V4_ONLY'
|
||||
IP_V6_ONLY = 'IP_V6_ONLY'
|
||||
IP_V4_PREFERRED = 'IP_V4_PREFERRED'
|
||||
IP_V6_PREFERRED = 'IP_V6_PREFERRED'
|
||||
|
||||
# search scope
|
||||
BASE = 'BASE'
|
||||
LEVEL = 'LEVEL'
|
||||
SUBTREE = 'SUBTREE'
|
||||
|
||||
# search alias
|
||||
DEREF_NEVER = 'NEVER'
|
||||
DEREF_SEARCH = 'SEARCH'
|
||||
DEREF_BASE = 'FINDING_BASE'
|
||||
DEREF_ALWAYS = 'ALWAYS'
|
||||
|
||||
# search attributes
|
||||
ALL_ATTRIBUTES = '*'
|
||||
NO_ATTRIBUTES = '1.1' # as per RFC 4511
|
||||
ALL_OPERATIONAL_ATTRIBUTES = '+' # as per RFC 3673
|
||||
|
||||
# modify type
|
||||
MODIFY_ADD = 'MODIFY_ADD'
|
||||
MODIFY_DELETE = 'MODIFY_DELETE'
|
||||
MODIFY_REPLACE = 'MODIFY_REPLACE'
|
||||
MODIFY_INCREMENT = 'MODIFY_INCREMENT'
|
||||
|
||||
# client strategies
|
||||
SYNC = 'SYNC'
|
||||
ASYNC = 'ASYNC'
|
||||
LDIF = 'LDIF'
|
||||
RESTARTABLE = 'RESTARTABLE'
|
||||
REUSABLE = 'REUSABLE'
|
||||
MOCK_SYNC = 'MOCK_SYNC'
|
||||
MOCK_ASYNC = 'MOCK_ASYNC'
|
||||
ASYNC_STREAM = 'ASYNC_STREAM'
|
||||
|
||||
# get rootDSE info
|
||||
NONE = 'NO_INFO'
|
||||
DSA = 'DSA'
|
||||
SCHEMA = 'SCHEMA'
|
||||
ALL = 'ALL'
|
||||
|
||||
OFFLINE_EDIR_8_8_8 = 'EDIR_8_8_8'
|
||||
OFFLINE_AD_2012_R2 = 'AD_2012_R2'
|
||||
OFFLINE_SLAPD_2_4 = 'SLAPD_2_4'
|
||||
OFFLINE_DS389_1_3_3 = 'DS389_1_3_3'
|
||||
|
||||
# server pooling
|
||||
FIRST = 'FIRST'
|
||||
ROUND_ROBIN = 'ROUND_ROBIN'
|
||||
RANDOM = 'RANDOM'
|
||||
|
||||
# Hashed password
|
||||
HASHED_NONE = 'PLAIN'
|
||||
HASHED_SHA = 'SHA'
|
||||
HASHED_SHA256 = 'SHA256'
|
||||
HASHED_SHA384 = 'SHA384'
|
||||
HASHED_SHA512 = 'SHA512'
|
||||
HASHED_MD5 = 'MD5'
|
||||
HASHED_SALTED_SHA = 'SALTED_SHA'
|
||||
HASHED_SALTED_SHA256 = 'SALTED_SHA256'
|
||||
HASHED_SALTED_SHA384 = 'SALTED_SHA384'
|
||||
HASHED_SALTED_SHA512 = 'SALTED_SHA512'
|
||||
HASHED_SALTED_MD5 = 'SALTED_MD5'
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
NUMERIC_TYPES = (int, float)
|
||||
INTEGER_TYPES = (int, )
|
||||
else:
|
||||
NUMERIC_TYPES = (int, long, float)
|
||||
INTEGER_TYPES = (int, long)
|
||||
|
||||
# types for string and sequence
|
||||
if str is not bytes: # Python 3
|
||||
STRING_TYPES = (str, )
|
||||
SEQUENCE_TYPES = (set, list, tuple, GeneratorType, type(dict().keys())) # dict.keys() is a iterable memoryview in Python 3
|
||||
else: # Python 2
|
||||
try:
|
||||
from future.types.newstr import newstr
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
STRING_TYPES = (str, unicode)
|
||||
SEQUENCE_TYPES = (set, list, tuple, GeneratorType)
|
||||
|
||||
# centralized imports # must be at the end of the __init__.py file
|
||||
from .version import __author__, __version__, __email__, __description__, __status__, __license__, __url__
|
||||
from .utils.config import get_config_parameter, set_config_parameter
|
||||
from .core.server import Server
|
||||
from .core.connection import Connection
|
||||
from .core.tls import Tls
|
||||
from .core.pooling import ServerPool
|
||||
from .abstract.objectDef import ObjectDef
|
||||
from .abstract.attrDef import AttrDef
|
||||
from .abstract.attribute import Attribute, WritableAttribute, OperationalAttribute
|
||||
from .abstract.entry import Entry, WritableEntry
|
||||
from .abstract.cursor import Reader, Writer
|
||||
from .protocol.rfc4512 import DsaInfo, SchemaInfo
|
@ -0,0 +1,50 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.08.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
STATUS_INIT = 'Initialized' # The entry object is initialized
|
||||
STATUS_VIRTUAL = 'Virtual' # The entry is a new writable entry, still empty
|
||||
STATUS_MANDATORY_MISSING = 'Missing mandatory attributes' # The entry has some mandatory attributes missing
|
||||
STATUS_READ = 'Read' # The entry has been read
|
||||
STATUS_WRITABLE = 'Writable' # The entry has been made writable, still no changes
|
||||
STATUS_PENDING_CHANGES = 'Pending changes' # The entry has some changes to commit, mandatory attributes are present
|
||||
STATUS_COMMITTED = 'Committed' # The entry changes has been committed
|
||||
STATUS_READY_FOR_DELETION = 'Ready for deletion' # The entry is set to be deleted
|
||||
STATUS_READY_FOR_MOVING = 'Ready for moving' # The entry is set to be moved in the DIT
|
||||
STATUS_READY_FOR_RENAMING = 'Ready for renaming' # The entry is set to be renamed
|
||||
STATUS_DELETED = 'Deleted' # The entry has been deleted
|
||||
|
||||
STATUSES = [STATUS_INIT,
|
||||
STATUS_VIRTUAL,
|
||||
STATUS_MANDATORY_MISSING,
|
||||
STATUS_READ,
|
||||
STATUS_WRITABLE,
|
||||
STATUS_PENDING_CHANGES,
|
||||
STATUS_COMMITTED,
|
||||
STATUS_READY_FOR_DELETION,
|
||||
STATUS_READY_FOR_MOVING,
|
||||
STATUS_READY_FOR_RENAMING,
|
||||
STATUS_DELETED]
|
||||
|
||||
INITIAL_STATUSES = [STATUS_READ, STATUS_WRITABLE, STATUS_VIRTUAL]
|
121
thesisenv/lib/python3.6/site-packages/ldap3/abstract/attrDef.py
Normal file
121
thesisenv/lib/python3.6/site-packages/ldap3/abstract/attrDef.py
Normal file
@ -0,0 +1,121 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.01.11
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import linesep
|
||||
|
||||
from .. import SEQUENCE_TYPES
|
||||
from ..core.exceptions import LDAPKeyError
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
|
||||
|
||||
|
||||
class AttrDef(object):
|
||||
"""Hold the definition of an attribute
|
||||
|
||||
:param name: the real attribute name
|
||||
:type name: string
|
||||
:param key: the friendly name to use in queries and when accessing the attribute, default to the real attribute name
|
||||
:type key: string
|
||||
:param validate: called to check if the value in the query is valid, the callable is called with the value parameter
|
||||
:type validate: callable
|
||||
:param pre_query: called to transform values returned by search
|
||||
:type pre_query: callable
|
||||
:param post_query: called to transform values returned by search
|
||||
:type post_query: callable
|
||||
:param default: value returned when the attribute is absent (defaults to NotImplemented to allow use of None as default)
|
||||
:type default: string, integer
|
||||
:param dereference_dn: reference to an ObjectDef instance. When the attribute value contains a dn it will be searched and substituted in the entry
|
||||
:type dereference_dn: ObjectDef
|
||||
:param description: custom attribute description
|
||||
:type description: string
|
||||
:param mandatory: specify if attribute is defined as mandatory in LDAP schema
|
||||
:type mandatory: boolean
|
||||
"""
|
||||
|
||||
def __init__(self, name, key=None, validate=None, pre_query=None, post_query=None, default=NotImplemented, dereference_dn=None, description=None, mandatory=False, single_value=None, alias=None):
|
||||
self.name = name
|
||||
self.key = ''.join(key.split()) if key else name # key set to name if not present
|
||||
self.validate = validate
|
||||
self.pre_query = pre_query
|
||||
self.post_query = post_query
|
||||
self.default = default
|
||||
self.dereference_dn = dereference_dn
|
||||
self.description = description
|
||||
self.mandatory = mandatory
|
||||
self.single_value = single_value
|
||||
self.oid_info = None
|
||||
if not alias:
|
||||
self.other_names = None
|
||||
elif isinstance(alias, SEQUENCE_TYPES): # multiple aliases
|
||||
self.\
|
||||
other_names = set(alias)
|
||||
else: # single alias
|
||||
self.other_names = set([alias]) # python 2 compatibility
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated AttrDef: <%r>', self)
|
||||
|
||||
def __repr__(self):
|
||||
r = 'ATTR: ' + ', '.join([self.key] + list(self.other_names)) if self.other_names else self.key
|
||||
r += '' if self.name == self.key else ' [' + self.name + ']'
|
||||
r += '' if self.default is NotImplemented else ' - default: ' + str(self.default)
|
||||
r += '' if self.mandatory is None else ' - mandatory: ' + str(self.mandatory)
|
||||
r += '' if self.single_value is None else ' - single_value: ' + str(self.single_value)
|
||||
r += '' if not self.dereference_dn else ' - dereference_dn: ' + str(self.dereference_dn)
|
||||
r += '' if not self.description else ' - description: ' + str(self.description)
|
||||
if self.oid_info:
|
||||
for line in str(self.oid_info).split(linesep):
|
||||
r += linesep + ' ' + line
|
||||
return r
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, AttrDef):
|
||||
return self.key == other.key
|
||||
|
||||
return False
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, AttrDef):
|
||||
return self.key < other.key
|
||||
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
if self.key:
|
||||
return hash(self.key)
|
||||
else:
|
||||
return id(self) # unique for each instance
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if hasattr(self, 'key') and key == 'key': # key cannot be changed because is being used for __hash__
|
||||
error_message = 'key \'%s\' already set' % key
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
else:
|
||||
object.__setattr__(self, key, value)
|
@ -0,0 +1,285 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.01.06
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import linesep
|
||||
|
||||
from .. import MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE, SEQUENCE_TYPES
|
||||
from ..core.exceptions import LDAPCursorError
|
||||
from ..utils.repr import to_stdout_encoding
|
||||
from . import STATUS_PENDING_CHANGES, STATUS_VIRTUAL, STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class Attribute(object):
|
||||
"""Attribute/values object, it includes the search result (after post_query transformation) of each attribute in an entry
|
||||
|
||||
Attribute object is read only
|
||||
|
||||
- values: contain the processed attribute values
|
||||
- raw_values': contain the unprocessed attribute values
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, attr_def, entry, cursor):
|
||||
self.key = attr_def.key
|
||||
self.definition = attr_def
|
||||
self.values = []
|
||||
self.raw_values = []
|
||||
self.response = None
|
||||
self.entry = entry
|
||||
self.cursor = cursor
|
||||
other_names = [name for name in attr_def.oid_info.name if self.key.lower() != name.lower()] if attr_def.oid_info else None
|
||||
self.other_names = set(other_names) if other_names else None # self.other_names is None if there are no short names, else is a set of secondary names
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.values) == 1:
|
||||
r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])
|
||||
elif len(self.values) > 1:
|
||||
r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])
|
||||
filler = ' ' * (len(self.key) + 6)
|
||||
for value in self.values[1:]:
|
||||
r += linesep + filler + to_stdout_encoding(value)
|
||||
else:
|
||||
r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding('<no value>')
|
||||
|
||||
return r
|
||||
|
||||
def __str__(self):
|
||||
if len(self.values) == 1:
|
||||
return to_stdout_encoding(self.values[0])
|
||||
else:
|
||||
return to_stdout_encoding(self.values)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.values)
|
||||
|
||||
def __iter__(self):
|
||||
return self.values.__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.values[item]
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.value == other:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
:return: The single value or a list of values of the attribute.
|
||||
"""
|
||||
if not self.values:
|
||||
return None
|
||||
|
||||
return self.values[0] if len(self.values) == 1 else self.values
|
||||
|
||||
|
||||
class OperationalAttribute(Attribute):
|
||||
"""Operational attribute/values object. Include the search result of an
|
||||
operational attribute in an entry
|
||||
|
||||
OperationalAttribute object is read only
|
||||
|
||||
- values: contains the processed attribute values
|
||||
- raw_values: contains the unprocessed attribute values
|
||||
|
||||
It may not have an AttrDef
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.values) == 1:
|
||||
r = to_stdout_encoding(self.key) + ' [OPERATIONAL]: ' + to_stdout_encoding(self.values[0])
|
||||
elif len(self.values) > 1:
|
||||
r = to_stdout_encoding(self.key) + ' [OPERATIONAL]: ' + to_stdout_encoding(self.values[0])
|
||||
filler = ' ' * (len(self.key) + 6)
|
||||
for value in sorted(self.values[1:]):
|
||||
r += linesep + filler + to_stdout_encoding(value)
|
||||
else:
|
||||
r = ''
|
||||
|
||||
return r
|
||||
|
||||
|
||||
class WritableAttribute(Attribute):
|
||||
def __repr__(self):
|
||||
filler = ' ' * (len(self.key) + 6)
|
||||
if len(self.values) == 1:
|
||||
r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])
|
||||
elif len(self.values) > 1:
|
||||
r = to_stdout_encoding(self.key) + ': ' + to_stdout_encoding(self.values[0])
|
||||
for value in self.values[1:]:
|
||||
r += linesep + filler + to_stdout_encoding(value)
|
||||
else:
|
||||
r = to_stdout_encoding(self.key) + to_stdout_encoding(': <Virtual>')
|
||||
if self.definition.name in self.entry._changes:
|
||||
r += linesep + filler + 'CHANGES: ' + str(self.entry._changes[self.definition.name])
|
||||
return r
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.add(other)
|
||||
return Ellipsis # hack to avoid calling set() in entry __setattr__
|
||||
|
||||
def __isub__(self, other):
|
||||
self.delete(other)
|
||||
return Ellipsis # hack to avoid calling set_value in entry __setattr__
|
||||
|
||||
def _update_changes(self, changes, remove_old=False):
|
||||
# checks for friendly key in AttrDef and uses the real attribute name
|
||||
if self.definition and self.definition.name:
|
||||
key = self.definition.name
|
||||
else:
|
||||
key = self.key
|
||||
|
||||
if key not in self.entry._changes or remove_old: # remove old changes (for removing attribute)
|
||||
self.entry._changes[key] = []
|
||||
|
||||
self.entry._changes[key].append(changes)
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'updated changes <%r> for <%s> attribute in <%s> entry', changes, self.key, self.entry.entry_dn)
|
||||
self.entry._state.set_status(STATUS_PENDING_CHANGES)
|
||||
|
||||
def add(self, values):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'adding %r to <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)
|
||||
# new value for attribute to commit with a MODIFY_ADD
|
||||
if self.entry._state._initial_status == STATUS_VIRTUAL:
|
||||
error_message = 'cannot add an attribute value in a new entry'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:
|
||||
error_message = self.entry.entry_status + ' - cannot add attributes'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if values is None:
|
||||
error_message = 'value to add cannot be None'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if values is not None:
|
||||
validated = self.definition.validate(values) # returns True, False or a value to substitute to the actual values
|
||||
if validated is False:
|
||||
error_message = 'value \'%s\' non valid for attribute \'%s\'' % (values, self.key)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
elif validated is not True: # a valid LDAP value equivalent to the actual values
|
||||
values = validated
|
||||
self._update_changes((MODIFY_ADD, values if isinstance(values, SEQUENCE_TYPES) else [values]))
|
||||
|
||||
def set(self, values):
|
||||
# new value for attribute to commit with a MODIFY_REPLACE, old values are deleted
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'setting %r to <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)
|
||||
if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:
|
||||
error_message = self.entry.entry_status + ' - cannot set attributes'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if values is None:
|
||||
error_message = 'new value cannot be None'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
validated = self.definition.validate(values) # returns True, False or a value to substitute to the actual values
|
||||
if validated is False:
|
||||
error_message = 'value \'%s\' non valid for attribute \'%s\'' % (values, self.key)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
elif validated is not True: # a valid LDAP value equivalent to the actual values
|
||||
values = validated
|
||||
self._update_changes((MODIFY_REPLACE, values if isinstance(values, SEQUENCE_TYPES) else [values]), remove_old=True)
|
||||
|
||||
def delete(self, values):
|
||||
# value for attribute to delete in commit with a MODIFY_DELETE
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'deleting %r from <%s> attribute in <%s> entry', values, self.key, self.entry.entry_dn)
|
||||
if self.entry._state._initial_status == STATUS_VIRTUAL:
|
||||
error_message = 'cannot delete an attribute value in a new entry'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:
|
||||
error_message = self.entry.entry_status + ' - cannot delete attributes'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if values is None:
|
||||
error_message = 'value to delete cannot be None'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if not isinstance(values, SEQUENCE_TYPES):
|
||||
values = [values]
|
||||
for single_value in values:
|
||||
if single_value not in self.values:
|
||||
error_message = 'value \'%s\' not present in \'%s\'' % (single_value, ', '.join(self.values))
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self._update_changes((MODIFY_DELETE, values))
|
||||
|
||||
def remove(self):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'removing <%s> attribute in <%s> entry', self.key, self.entry.entry_dn)
|
||||
if self.entry._state._initial_status == STATUS_VIRTUAL:
|
||||
error_message = 'cannot remove an attribute in a new entry'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if self.entry.entry_status in [STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING]:
|
||||
error_message = self.entry.entry_status + ' - cannot remove attributes'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self._update_changes((MODIFY_REPLACE, []), True)
|
||||
|
||||
def discard(self):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'discarding <%s> attribute in <%s> entry', self.key, self.entry.entry_dn)
|
||||
del self.entry._changes[self.key]
|
||||
if not self.entry._changes:
|
||||
self.entry._state.set_status(self.entry._state._initial_status)
|
||||
|
||||
@property
|
||||
def virtual(self):
|
||||
return False if len(self.values) else True
|
||||
|
||||
@property
|
||||
def changes(self):
|
||||
if self.key in self.entry._changes:
|
||||
return self.entry._changes[self.key]
|
||||
return None
|
904
thesisenv/lib/python3.6/site-packages/ldap3/abstract/cursor.py
Normal file
904
thesisenv/lib/python3.6/site-packages/ldap3/abstract/cursor.py
Normal file
@ -0,0 +1,904 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.01.06
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from collections import namedtuple
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from os import linesep
|
||||
from time import sleep
|
||||
|
||||
from . import STATUS_VIRTUAL, STATUS_READ, STATUS_WRITABLE
|
||||
from .. import SUBTREE, LEVEL, DEREF_ALWAYS, DEREF_NEVER, BASE, SEQUENCE_TYPES, STRING_TYPES, get_config_parameter
|
||||
from ..abstract import STATUS_PENDING_CHANGES
|
||||
from .attribute import Attribute, OperationalAttribute, WritableAttribute
|
||||
from .attrDef import AttrDef
|
||||
from .objectDef import ObjectDef
|
||||
from .entry import Entry, WritableEntry
|
||||
from ..core.exceptions import LDAPCursorError, LDAPObjectDereferenceError
|
||||
from ..core.results import RESULT_SUCCESS
|
||||
from ..utils.ciDict import CaseInsensitiveWithAliasDict
|
||||
from ..utils.dn import safe_dn, safe_rdn
|
||||
from ..utils.conv import to_raw
|
||||
from ..utils.config import get_config_parameter
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
|
||||
from ..protocol.oid import ATTRIBUTE_DIRECTORY_OPERATION, ATTRIBUTE_DISTRIBUTED_OPERATION, ATTRIBUTE_DSA_OPERATION, CLASS_AUXILIARY
|
||||
|
||||
Operation = namedtuple('Operation', ('request', 'result', 'response'))
|
||||
|
||||
|
||||
def _ret_search_value(value):
|
||||
return value[0] + '=' + value[1:] if value[0] in '<>~' and value[1] != '=' else value
|
||||
|
||||
|
||||
def _create_query_dict(query_text):
|
||||
"""
|
||||
Create a dictionary with query key:value definitions
|
||||
query_text is a comma delimited key:value sequence
|
||||
"""
|
||||
query_dict = dict()
|
||||
if query_text:
|
||||
for arg_value_str in query_text.split(','):
|
||||
if ':' in arg_value_str:
|
||||
arg_value_list = arg_value_str.split(':')
|
||||
query_dict[arg_value_list[0].strip()] = arg_value_list[1].strip()
|
||||
|
||||
return query_dict
|
||||
|
||||
|
||||
class Cursor(object):
|
||||
# entry_class and attribute_class define the type of entry and attribute used by the cursor
|
||||
# entry_initial_status defines the initial status of a entry
|
||||
# entry_class = Entry, must be defined in subclasses
|
||||
# attribute_class = Attribute, must be defined in subclasses
|
||||
# entry_initial_status = STATUS, must be defined in subclasses
|
||||
|
||||
def __init__(self, connection, object_def, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):
|
||||
conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
|
||||
self.connection = connection
|
||||
self.get_operational_attributes = get_operational_attributes
|
||||
if connection._deferred_bind or connection._deferred_open: # probably a lazy connection, tries to bind
|
||||
connection._fire_deferred()
|
||||
|
||||
if isinstance(object_def, (STRING_TYPES, SEQUENCE_TYPES)):
|
||||
object_def = ObjectDef(object_def, connection.server.schema, auxiliary_class=auxiliary_class)
|
||||
self.definition = object_def
|
||||
if attributes: # checks if requested attributes are defined in ObjectDef
|
||||
not_defined_attributes = []
|
||||
if isinstance(attributes, STRING_TYPES):
|
||||
attributes = [attributes]
|
||||
|
||||
for attribute in attributes:
|
||||
if attribute not in self.definition._attributes and attribute.lower() not in conf_attributes_excluded_from_object_def:
|
||||
not_defined_attributes.append(attribute)
|
||||
|
||||
if not_defined_attributes:
|
||||
error_message = 'Attributes \'%s\' non in definition' % ', '.join(not_defined_attributes)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
self.attributes = set(attributes) if attributes else set([attr.name for attr in self.definition])
|
||||
self.controls = controls
|
||||
self.execution_time = None
|
||||
self.entries = []
|
||||
self.schema = self.connection.server.schema
|
||||
self._do_not_reset = False # used for refreshing entry in entry_refresh() without removing all entries from the Cursor
|
||||
self._operation_history = list() # a list storing all the requests, results and responses for the last cursor operation
|
||||
|
||||
def __repr__(self):
|
||||
r = 'CURSOR : ' + self.__class__.__name__ + linesep
|
||||
r += 'CONN : ' + str(self.connection) + linesep
|
||||
r += 'DEFS : ' + ', '.join(self.definition._object_class)
|
||||
if self.definition._auxiliary_class:
|
||||
r += ' [AUX: ' + ', '.join(self.definition._auxiliary_class) + ']'
|
||||
r += linesep
|
||||
# for attr_def in sorted(self.definition):
|
||||
# r += (attr_def.key if attr_def.key == attr_def.name else (attr_def.key + ' <' + attr_def.name + '>')) + ', '
|
||||
# if r[-2] == ',':
|
||||
# r = r[:-2]
|
||||
# r += ']' + linesep
|
||||
if hasattr(self, 'attributes'):
|
||||
r += 'ATTRS : ' + repr(sorted(self.attributes)) + (' [OPERATIONAL]' if self.get_operational_attributes else '') + linesep
|
||||
if isinstance(self, Reader):
|
||||
if hasattr(self, 'base'):
|
||||
r += 'BASE : ' + repr(self.base) + (' [SUB]' if self.sub_tree else ' [LEVEL]') + linesep
|
||||
if hasattr(self, '_query') and self._query:
|
||||
r += 'QUERY : ' + repr(self._query) + ('' if '(' in self._query else (' [AND]' if self.components_in_and else ' [OR]')) + linesep
|
||||
if hasattr(self, 'validated_query') and self.validated_query:
|
||||
r += 'PARSED : ' + repr(self.validated_query) + ('' if '(' in self._query else (' [AND]' if self.components_in_and else ' [OR]')) + linesep
|
||||
if hasattr(self, 'query_filter') and self.query_filter:
|
||||
r += 'FILTER : ' + repr(self.query_filter) + linesep
|
||||
|
||||
if hasattr(self, 'execution_time') and self.execution_time:
|
||||
r += 'ENTRIES: ' + str(len(self.entries))
|
||||
r += ' [executed at: ' + str(self.execution_time.isoformat()) + ']' + linesep
|
||||
|
||||
if self.failed:
|
||||
r += 'LAST OPERATION FAILED [' + str(len(self.errors)) + ' failure' + ('s' if len(self.errors) > 1 else '') + ' at operation' + ('s ' if len(self.errors) > 1 else ' ') + ', '.join([str(i) for i, error in enumerate(self.operations) if error.result['result'] != RESULT_SUCCESS]) + ']'
|
||||
|
||||
return r
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __iter__(self):
|
||||
return self.entries.__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""Return indexed item, if index is not found then try to sequentially search in DN of entries.
|
||||
If only one entry is found return it else raise a KeyError exception. The exception message
|
||||
includes the number of entries that matches, if less than 10 entries match then show the DNs
|
||||
in the exception message.
|
||||
"""
|
||||
try:
|
||||
return self.entries[item]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if isinstance(item, STRING_TYPES):
|
||||
found = self.match_dn(item)
|
||||
|
||||
if len(found) == 1:
|
||||
return found[0]
|
||||
elif len(found) > 1:
|
||||
error_message = 'Multiple entries found: %d entries match the text in dn' % len(found) + ('' if len(found) > 10 else (' [' + '; '.join([e.entry_dn for e in found]) + ']'))
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise KeyError(error_message)
|
||||
|
||||
error_message = 'no entry found'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise KeyError(error_message)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.entries)
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
def __bool__(self): # needed to make the cursor appears as existing in "if cursor:" even if there are no entries
|
||||
return True
|
||||
else: # Python 2
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def _get_attributes(self, response, attr_defs, entry):
|
||||
"""Assign the result of the LDAP query to the Entry object dictionary.
|
||||
|
||||
If the optional 'post_query' callable is present in the AttrDef it is called with each value of the attribute and the callable result is stored in the attribute.
|
||||
|
||||
Returns the default value for missing attributes.
|
||||
If the 'dereference_dn' in AttrDef is a ObjectDef then the attribute values are treated as distinguished name and the relevant entry is retrieved and stored in the attribute value.
|
||||
|
||||
"""
|
||||
conf_operational_attribute_prefix = get_config_parameter('ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX')
|
||||
conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
|
||||
attributes = CaseInsensitiveWithAliasDict()
|
||||
used_attribute_names = set()
|
||||
for attr in attr_defs:
|
||||
attr_def = attr_defs[attr]
|
||||
attribute_name = None
|
||||
for attr_name in response['attributes']:
|
||||
if attr_def.name.lower() == attr_name.lower():
|
||||
attribute_name = attr_name
|
||||
break
|
||||
|
||||
if attribute_name or attr_def.default is not NotImplemented: # attribute value found in result or default value present - NotImplemented allows use of None as default
|
||||
attribute = self.attribute_class(attr_def, entry, self)
|
||||
attribute.response = response
|
||||
attribute.raw_values = response['raw_attributes'][attribute_name] if attribute_name else None
|
||||
if attr_def.post_query and attr_def.name in response['attributes'] and response['raw_attributes'] != list():
|
||||
attribute.values = attr_def.post_query(attr_def.key, response['attributes'][attribute_name])
|
||||
else:
|
||||
if attr_def.default is NotImplemented or (attribute_name and response['raw_attributes'][attribute_name] != list()):
|
||||
attribute.values = response['attributes'][attribute_name]
|
||||
else:
|
||||
attribute.values = attr_def.default if isinstance(attr_def.default, SEQUENCE_TYPES) else [attr_def.default]
|
||||
if not isinstance(attribute.values, list): # force attribute values to list (if attribute is single-valued)
|
||||
attribute.values = [attribute.values]
|
||||
if attr_def.dereference_dn: # try to get object referenced in value
|
||||
if attribute.values:
|
||||
temp_reader = Reader(self.connection, attr_def.dereference_dn, base='', get_operational_attributes=self.get_operational_attributes, controls=self.controls)
|
||||
temp_values = []
|
||||
for element in attribute.values:
|
||||
if entry.entry_dn != element:
|
||||
temp_values.append(temp_reader.search_object(element))
|
||||
else:
|
||||
error_message = 'object %s is referencing itself in the \'%s\' attribute' % (entry.entry_dn, attribute.definition.name)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPObjectDereferenceError(error_message)
|
||||
del temp_reader # remove the temporary Reader
|
||||
attribute.values = temp_values
|
||||
attributes[attribute.key] = attribute
|
||||
if attribute.other_names:
|
||||
attributes.set_alias(attribute.key, attribute.other_names)
|
||||
if attr_def.other_names:
|
||||
attributes.set_alias(attribute.key, attr_def.other_names)
|
||||
used_attribute_names.add(attribute_name)
|
||||
|
||||
if self.attributes:
|
||||
used_attribute_names.update(self.attributes)
|
||||
|
||||
for attribute_name in response['attributes']:
|
||||
if attribute_name not in used_attribute_names:
|
||||
operational_attribute = False
|
||||
# check if the type is an operational attribute
|
||||
if attribute_name in self.schema.attribute_types:
|
||||
if self.schema.attribute_types[attribute_name].no_user_modification or self.schema.attribute_types[attribute_name].usage in [ATTRIBUTE_DIRECTORY_OPERATION, ATTRIBUTE_DISTRIBUTED_OPERATION, ATTRIBUTE_DSA_OPERATION]:
|
||||
operational_attribute = True
|
||||
else:
|
||||
operational_attribute = True
|
||||
if not operational_attribute and attribute_name not in attr_defs and attribute_name.lower() not in conf_attributes_excluded_from_object_def:
|
||||
error_message = 'attribute \'%s\' not in object class \'%s\' for entry %s' % (attribute_name, ', '.join(entry.entry_definition._object_class), entry.entry_dn)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
attribute = OperationalAttribute(AttrDef(conf_operational_attribute_prefix + attribute_name), entry, self)
|
||||
attribute.raw_values = response['raw_attributes'][attribute_name]
|
||||
attribute.values = response['attributes'][attribute_name] if isinstance(response['attributes'][attribute_name], SEQUENCE_TYPES) else [response['attributes'][attribute_name]]
|
||||
if (conf_operational_attribute_prefix + attribute_name) not in attributes:
|
||||
attributes[conf_operational_attribute_prefix + attribute_name] = attribute
|
||||
|
||||
return attributes
|
||||
|
||||
def match_dn(self, dn):
|
||||
"""Return entries with text in DN"""
|
||||
matched = []
|
||||
for entry in self.entries:
|
||||
if dn.lower() in entry.entry_dn.lower():
|
||||
matched.append(entry)
|
||||
return matched
|
||||
|
||||
def match(self, attributes, value):
|
||||
"""Return entries with text in one of the specified attributes"""
|
||||
matched = []
|
||||
if not isinstance(attributes, SEQUENCE_TYPES):
|
||||
attributes = [attributes]
|
||||
|
||||
for entry in self.entries:
|
||||
found = False
|
||||
for attribute in attributes:
|
||||
if attribute in entry:
|
||||
for attr_value in entry[attribute].values:
|
||||
if hasattr(attr_value, 'lower') and hasattr(value, 'lower') and value.lower() in attr_value.lower():
|
||||
found = True
|
||||
elif value == attr_value:
|
||||
found = True
|
||||
if found:
|
||||
matched.append(entry)
|
||||
break
|
||||
if found:
|
||||
break
|
||||
# checks raw values, tries to convert value to byte
|
||||
raw_value = to_raw(value)
|
||||
if isinstance(raw_value, (bytes, bytearray)):
|
||||
for attr_value in entry[attribute].raw_values:
|
||||
if hasattr(attr_value, 'lower') and hasattr(raw_value, 'lower') and raw_value.lower() in attr_value.lower():
|
||||
found = True
|
||||
elif raw_value == attr_value:
|
||||
found = True
|
||||
if found:
|
||||
matched.append(entry)
|
||||
break
|
||||
if found:
|
||||
break
|
||||
return matched
|
||||
|
||||
def _create_entry(self, response):
|
||||
if not response['type'] == 'searchResEntry':
|
||||
return None
|
||||
|
||||
entry = self.entry_class(response['dn'], self) # define an Entry (writable or readonly), as specified in the cursor definition
|
||||
entry._state.attributes = self._get_attributes(response, self.definition._attributes, entry)
|
||||
entry._state.entry_raw_attributes = deepcopy(response['raw_attributes'])
|
||||
|
||||
entry._state.response = response
|
||||
entry._state.read_time = datetime.now()
|
||||
entry._state.set_status(self.entry_initial_status)
|
||||
for attr in entry: # returns the whole attribute object
|
||||
entry.__dict__[attr.key] = attr
|
||||
|
||||
return entry
|
||||
|
||||
def _execute_query(self, query_scope, attributes):
|
||||
if not self.connection:
|
||||
error_message = 'no connection established'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
old_query_filter = None
|
||||
if query_scope == BASE: # requesting a single object so an always-valid filter is set
|
||||
if hasattr(self, 'query_filter'): # only Reader has a query filter
|
||||
old_query_filter = self.query_filter
|
||||
self.query_filter = '(objectclass=*)'
|
||||
else:
|
||||
self._create_query_filter()
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'executing query - base: %s - filter: %s - scope: %s for <%s>', self.base, self.query_filter, query_scope, self)
|
||||
with self.connection:
|
||||
result = self.connection.search(search_base=self.base,
|
||||
search_filter=self.query_filter,
|
||||
search_scope=query_scope,
|
||||
dereference_aliases=self.dereference_aliases,
|
||||
attributes=attributes if attributes else list(self.attributes),
|
||||
get_operational_attributes=self.get_operational_attributes,
|
||||
controls=self.controls)
|
||||
if not self.connection.strategy.sync:
|
||||
response, result, request = self.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.connection.response
|
||||
result = self.connection.result
|
||||
request = self.connection.request
|
||||
|
||||
self._store_operation_in_history(request, result, response)
|
||||
|
||||
if self._do_not_reset: # trick to not remove entries when using _refresh()
|
||||
return self._create_entry(response[0])
|
||||
|
||||
self.entries = []
|
||||
for r in response:
|
||||
entry = self._create_entry(r)
|
||||
if entry is not None:
|
||||
self.entries.append(entry)
|
||||
if 'objectClass' in entry:
|
||||
for object_class in entry.objectClass:
|
||||
if self.schema.object_classes[object_class].kind == CLASS_AUXILIARY and object_class not in self.definition._auxiliary_class:
|
||||
# add auxiliary class to object definition
|
||||
self.definition._auxiliary_class.append(object_class)
|
||||
self.definition._populate_attr_defs(object_class)
|
||||
self.execution_time = datetime.now()
|
||||
|
||||
if old_query_filter: # requesting a single object so an always-valid filter is set
|
||||
self.query_filter = old_query_filter
|
||||
|
||||
def remove(self, entry):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'removing entry <%s> in <%s>', entry, self)
|
||||
self.entries.remove(entry)
|
||||
|
||||
def _reset_history(self):
|
||||
self._operation_history = list()
|
||||
|
||||
def _store_operation_in_history(self, request, result, response):
|
||||
self._operation_history.append(Operation(request, result, response))
|
||||
|
||||
@property
|
||||
def operations(self):
|
||||
return self._operation_history
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
return [error for error in self._operation_history if error.result['result'] != RESULT_SUCCESS]
|
||||
|
||||
@property
|
||||
def failed(self):
|
||||
if hasattr(self, '_operation_history'):
|
||||
return any([error.result['result'] != RESULT_SUCCESS for error in self._operation_history])
|
||||
|
||||
|
||||
class Reader(Cursor):
|
||||
"""Reader object to perform searches:
|
||||
|
||||
:param connection: the LDAP connection object to use
|
||||
:type connection: LDAPConnection
|
||||
:param object_def: the ObjectDef of the LDAP object returned
|
||||
:type object_def: ObjectDef
|
||||
:param query: the simplified query (will be transformed in an LDAP filter)
|
||||
:type query: str
|
||||
:param base: starting base of the search
|
||||
:type base: str
|
||||
:param components_in_and: specify if assertions in the query must all be satisfied or not (AND/OR)
|
||||
:type components_in_and: bool
|
||||
:param sub_tree: specify if the search must be performed ad Single Level (False) or Whole SubTree (True)
|
||||
:type sub_tree: bool
|
||||
:param get_operational_attributes: specify if operational attributes are returned or not
|
||||
:type get_operational_attributes: bool
|
||||
:param controls: controls to be used in search
|
||||
:type controls: tuple
|
||||
|
||||
"""
|
||||
entry_class = Entry # entries are read_only
|
||||
attribute_class = Attribute # attributes are read_only
|
||||
entry_initial_status = STATUS_READ
|
||||
|
||||
def __init__(self, connection, object_def, base, query='', components_in_and=True, sub_tree=True, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):
|
||||
Cursor.__init__(self, connection, object_def, get_operational_attributes, attributes, controls, auxiliary_class)
|
||||
self._components_in_and = components_in_and
|
||||
self.sub_tree = sub_tree
|
||||
self._query = query
|
||||
self.base = base
|
||||
self.dereference_aliases = DEREF_ALWAYS
|
||||
self.validated_query = None
|
||||
self._query_dict = dict()
|
||||
self._validated_query_dict = dict()
|
||||
self.query_filter = None
|
||||
self.reset()
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Reader Cursor: <%r>', self)
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
return self._query
|
||||
|
||||
@query.setter
|
||||
def query(self, value):
|
||||
self._query = value
|
||||
self.reset()
|
||||
|
||||
@property
|
||||
def components_in_and(self):
|
||||
return self._components_in_and
|
||||
|
||||
@components_in_and.setter
|
||||
def components_in_and(self, value):
|
||||
self._components_in_and = value
|
||||
self.reset()
|
||||
|
||||
def clear(self):
|
||||
"""Clear the Reader search parameters
|
||||
|
||||
"""
|
||||
self.dereference_aliases = DEREF_ALWAYS
|
||||
self._reset_history()
|
||||
|
||||
def reset(self):
|
||||
"""Clear all the Reader parameters
|
||||
|
||||
"""
|
||||
self.clear()
|
||||
self.validated_query = None
|
||||
self._query_dict = dict()
|
||||
self._validated_query_dict = dict()
|
||||
self.execution_time = None
|
||||
self.query_filter = None
|
||||
self.entries = []
|
||||
self._create_query_filter()
|
||||
|
||||
def _validate_query(self):
|
||||
"""Processes the text query and verifies that the requested friendly names are in the Reader dictionary
|
||||
If the AttrDef has a 'validate' property the callable is executed and if it returns False an Exception is raised
|
||||
|
||||
"""
|
||||
if not self._query_dict:
|
||||
self._query_dict = _create_query_dict(self._query)
|
||||
|
||||
query = ''
|
||||
for d in sorted(self._query_dict):
|
||||
attr = d[1:] if d[0] in '&|' else d
|
||||
for attr_def in self.definition:
|
||||
if ''.join(attr.split()).lower() == attr_def.key.lower():
|
||||
attr = attr_def.key
|
||||
break
|
||||
if attr in self.definition:
|
||||
vals = sorted(self._query_dict[d].split(';'))
|
||||
|
||||
query += (d[0] + attr if d[0] in '&|' else attr) + ': '
|
||||
for val in vals:
|
||||
val = val.strip()
|
||||
val_not = True if val[0] == '!' else False
|
||||
val_search_operator = '=' # default
|
||||
if val_not:
|
||||
if val[1:].lstrip()[0] not in '=<>~':
|
||||
value = val[1:].lstrip()
|
||||
else:
|
||||
val_search_operator = val[1:].lstrip()[0]
|
||||
value = val[1:].lstrip()[1:]
|
||||
else:
|
||||
if val[0] not in '=<>~':
|
||||
value = val.lstrip()
|
||||
else:
|
||||
val_search_operator = val[0]
|
||||
value = val[1:].lstrip()
|
||||
|
||||
if self.definition[attr].validate:
|
||||
validated = self.definition[attr].validate(value) # returns True, False or a value to substitute to the actual values
|
||||
if validated is False:
|
||||
error_message = 'validation failed for attribute %s and value %s' % (d, val)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
elif validated is not True: # a valid LDAP value equivalent to the actual values
|
||||
value = validated
|
||||
if val_not:
|
||||
query += '!' + val_search_operator + str(value)
|
||||
else:
|
||||
query += val_search_operator + str(value)
|
||||
|
||||
query += ';'
|
||||
query = query[:-1] + ', '
|
||||
else:
|
||||
error_message = 'attribute \'%s\' not in definition' % attr
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self.validated_query = query[:-2]
|
||||
self._validated_query_dict = _create_query_dict(self.validated_query)
|
||||
|
||||
def _create_query_filter(self):
|
||||
"""Converts the query dictionary to the filter text"""
|
||||
self.query_filter = ''
|
||||
|
||||
if self.definition._object_class:
|
||||
self.query_filter += '(&'
|
||||
if isinstance(self.definition._object_class, SEQUENCE_TYPES) and len(self.definition._object_class) == 1:
|
||||
self.query_filter += '(objectClass=' + self.definition._object_class[0] + ')'
|
||||
elif isinstance(self.definition._object_class, SEQUENCE_TYPES):
|
||||
self.query_filter += '(&'
|
||||
for object_class in self.definition._object_class:
|
||||
self.query_filter += '(objectClass=' + object_class + ')'
|
||||
self.query_filter += ')'
|
||||
else:
|
||||
error_message = 'object class must be a string or a list'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
if self._query and self._query.startswith('(') and self._query.endswith(')'): # query is already an LDAP filter
|
||||
if 'objectclass' not in self._query.lower():
|
||||
self.query_filter += self._query + ')' # if objectclass not in filter adds from definition
|
||||
else:
|
||||
self.query_filter = self._query
|
||||
return
|
||||
elif self._query: # if a simplified filter is present
|
||||
if not self.components_in_and:
|
||||
self.query_filter += '(|'
|
||||
elif not self.definition._object_class:
|
||||
self.query_filter += '(&'
|
||||
|
||||
self._validate_query()
|
||||
|
||||
attr_counter = 0
|
||||
for attr in sorted(self._validated_query_dict):
|
||||
attr_counter += 1
|
||||
multi = True if ';' in self._validated_query_dict[attr] else False
|
||||
vals = sorted(self._validated_query_dict[attr].split(';'))
|
||||
attr_def = self.definition[attr[1:]] if attr[0] in '&|' else self.definition[attr]
|
||||
if attr_def.pre_query:
|
||||
modvals = []
|
||||
for val in vals:
|
||||
modvals.append(val[0] + attr_def.pre_query(attr_def.key, val[1:]))
|
||||
vals = modvals
|
||||
if multi:
|
||||
if attr[0] in '&|':
|
||||
self.query_filter += '(' + attr[0]
|
||||
else:
|
||||
self.query_filter += '(|'
|
||||
|
||||
for val in vals:
|
||||
if val[0] == '!':
|
||||
self.query_filter += '(!(' + attr_def.name + _ret_search_value(val[1:]) + '))'
|
||||
else:
|
||||
self.query_filter += '(' + attr_def.name + _ret_search_value(val) + ')'
|
||||
if multi:
|
||||
self.query_filter += ')'
|
||||
|
||||
if not self.components_in_and:
|
||||
self.query_filter += '))'
|
||||
else:
|
||||
self.query_filter += ')'
|
||||
|
||||
if not self.definition._object_class and attr_counter == 1: # removes unneeded starting filter
|
||||
self.query_filter = self.query_filter[2: -1]
|
||||
|
||||
if self.query_filter == '(|)' or self.query_filter == '(&)': # removes empty filter
|
||||
self.query_filter = ''
|
||||
else: # no query, remove unneeded leading (&
|
||||
self.query_filter = self.query_filter[2:]
|
||||
|
||||
def search(self, attributes=None):
|
||||
"""Perform the LDAP search
|
||||
|
||||
:return: Entries found in search
|
||||
|
||||
"""
|
||||
self.clear()
|
||||
query_scope = SUBTREE if self.sub_tree else LEVEL
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'performing search in <%s>', self)
|
||||
self._execute_query(query_scope, attributes)
|
||||
|
||||
return self.entries
|
||||
|
||||
def search_object(self, entry_dn=None, attributes=None): # base must be a single dn
|
||||
"""Perform the LDAP search operation SINGLE_OBJECT scope
|
||||
|
||||
:return: Entry found in search
|
||||
|
||||
"""
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'performing object search in <%s>', self)
|
||||
self.clear()
|
||||
if entry_dn:
|
||||
old_base = self.base
|
||||
self.base = entry_dn
|
||||
self._execute_query(BASE, attributes)
|
||||
self.base = old_base
|
||||
else:
|
||||
self._execute_query(BASE, attributes)
|
||||
|
||||
return self.entries[0] if len(self.entries) > 0 else None
|
||||
|
||||
def search_level(self, attributes=None):
|
||||
"""Perform the LDAP search operation with SINGLE_LEVEL scope
|
||||
|
||||
:return: Entries found in search
|
||||
|
||||
"""
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'performing single level search in <%s>', self)
|
||||
self.clear()
|
||||
self._execute_query(LEVEL, attributes)
|
||||
|
||||
return self.entries
|
||||
|
||||
def search_subtree(self, attributes=None):
|
||||
"""Perform the LDAP search operation WHOLE_SUBTREE scope
|
||||
|
||||
:return: Entries found in search
|
||||
|
||||
"""
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'performing whole subtree search in <%s>', self)
|
||||
self.clear()
|
||||
self._execute_query(SUBTREE, attributes)
|
||||
|
||||
return self.entries
|
||||
|
||||
def _entries_generator(self, responses):
|
||||
for response in responses:
|
||||
yield self._create_entry(response)
|
||||
|
||||
def search_paged(self, paged_size, paged_criticality=True, generator=True, attributes=None):
|
||||
"""Perform a paged search, can be called as an Iterator
|
||||
|
||||
:param attributes: optional attributes to search
|
||||
:param paged_size: number of entries returned in each search
|
||||
:type paged_size: int
|
||||
:param paged_criticality: specify if server must not execute the search if it is not capable of paging searches
|
||||
:type paged_criticality: bool
|
||||
:param generator: if True the paged searches are executed while generating the entries,
|
||||
if False all the paged searches are execute before returning the generator
|
||||
:type generator: bool
|
||||
:return: Entries found in search
|
||||
|
||||
"""
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'performing paged search in <%s> with paged size %s', self, str(paged_size))
|
||||
if not self.connection:
|
||||
error_message = 'no connection established'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
self.clear()
|
||||
self._create_query_filter()
|
||||
self.entries = []
|
||||
self.execution_time = datetime.now()
|
||||
response = self.connection.extend.standard.paged_search(search_base=self.base,
|
||||
search_filter=self.query_filter,
|
||||
search_scope=SUBTREE if self.sub_tree else LEVEL,
|
||||
dereference_aliases=self.dereference_aliases,
|
||||
attributes=attributes if attributes else self.attributes,
|
||||
get_operational_attributes=self.get_operational_attributes,
|
||||
controls=self.controls,
|
||||
paged_size=paged_size,
|
||||
paged_criticality=paged_criticality,
|
||||
generator=generator)
|
||||
if generator:
|
||||
return self._entries_generator(response)
|
||||
else:
|
||||
return list(self._entries_generator(response))
|
||||
|
||||
|
||||
class Writer(Cursor):
|
||||
entry_class = WritableEntry
|
||||
attribute_class = WritableAttribute
|
||||
entry_initial_status = STATUS_WRITABLE
|
||||
|
||||
@staticmethod
|
||||
def from_cursor(cursor, connection=None, object_def=None, custom_validator=None):
|
||||
if connection is None:
|
||||
connection = cursor.connection
|
||||
if object_def is None:
|
||||
object_def = cursor.definition
|
||||
writer = Writer(connection, object_def, attributes=cursor.attributes)
|
||||
for entry in cursor.entries:
|
||||
if isinstance(cursor, Reader):
|
||||
entry.entry_writable(object_def, writer, custom_validator=custom_validator)
|
||||
elif isinstance(cursor, Writer):
|
||||
pass
|
||||
else:
|
||||
error_message = 'unknown cursor type %s' % str(type(cursor))
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s', error_message)
|
||||
raise LDAPCursorError(error_message)
|
||||
writer.execution_time = cursor.execution_time
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Writer Cursor <%r> from cursor <%r>', writer, cursor)
|
||||
return writer
|
||||
|
||||
@staticmethod
|
||||
def from_response(connection, object_def, response=None):
|
||||
if response is None:
|
||||
if not connection.strategy.sync:
|
||||
error_message = 'with asynchronous strategies response must be specified'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s', error_message)
|
||||
raise LDAPCursorError(error_message)
|
||||
elif connection.response:
|
||||
response = connection.response
|
||||
else:
|
||||
error_message = 'response not present'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s', error_message)
|
||||
raise LDAPCursorError(error_message)
|
||||
writer = Writer(connection, object_def)
|
||||
|
||||
for resp in response:
|
||||
if resp['type'] == 'searchResEntry':
|
||||
entry = writer._create_entry(resp)
|
||||
writer.entries.append(entry)
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Writer Cursor <%r> from response', writer)
|
||||
return writer
|
||||
|
||||
def __init__(self, connection, object_def, get_operational_attributes=False, attributes=None, controls=None, auxiliary_class=None):
|
||||
Cursor.__init__(self, connection, object_def, get_operational_attributes, attributes, controls, auxiliary_class)
|
||||
self.dereference_aliases = DEREF_NEVER
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Writer Cursor: <%r>', self)
|
||||
|
||||
def commit(self, refresh=True):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'committed changes for <%s>', self)
|
||||
self._reset_history()
|
||||
successful = True
|
||||
for entry in self.entries:
|
||||
if not entry.entry_commit_changes(refresh=refresh, controls=self.controls, clear_history=False):
|
||||
successful = False
|
||||
|
||||
self.execution_time = datetime.now()
|
||||
|
||||
return successful
|
||||
|
||||
def discard(self):
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'discarded changes for <%s>', self)
|
||||
for entry in self.entries:
|
||||
entry.entry_discard_changes()
|
||||
|
||||
def _refresh_object(self, entry_dn, attributes=None, tries=4, seconds=2, controls=None): # base must be a single dn
|
||||
"""Performs the LDAP search operation SINGLE_OBJECT scope
|
||||
|
||||
:return: Entry found in search
|
||||
|
||||
"""
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'refreshing object <%s> for <%s>', entry_dn, self)
|
||||
if not self.connection:
|
||||
error_message = 'no connection established'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
response = []
|
||||
with self.connection:
|
||||
counter = 0
|
||||
while counter < tries:
|
||||
result = self.connection.search(search_base=entry_dn,
|
||||
search_filter='(objectclass=*)',
|
||||
search_scope=BASE,
|
||||
dereference_aliases=DEREF_NEVER,
|
||||
attributes=attributes if attributes else self.attributes,
|
||||
get_operational_attributes=self.get_operational_attributes,
|
||||
controls=controls)
|
||||
if not self.connection.strategy.sync:
|
||||
response, result, request = self.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.connection.response
|
||||
result = self.connection.result
|
||||
request = self.connection.request
|
||||
|
||||
if result['result'] in [RESULT_SUCCESS]:
|
||||
break
|
||||
sleep(seconds)
|
||||
counter += 1
|
||||
self._store_operation_in_history(request, result, response)
|
||||
|
||||
if len(response) == 1:
|
||||
return self._create_entry(response[0])
|
||||
elif len(response) == 0:
|
||||
return None
|
||||
|
||||
error_message = 'more than 1 entry returned for a single object search'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
def new(self, dn):
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'creating new entry <%s> for <%s>', dn, self)
|
||||
dn = safe_dn(dn)
|
||||
for entry in self.entries: # checks if dn is already used in an cursor entry
|
||||
if entry.entry_dn == dn:
|
||||
error_message = 'dn already present in cursor'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
rdns = safe_rdn(dn, decompose=True)
|
||||
entry = self.entry_class(dn, self) # defines a new empty Entry
|
||||
for attr in entry.entry_mandatory_attributes: # defines all mandatory attributes as virtual
|
||||
entry._state.attributes[attr] = self.attribute_class(entry._state.definition[attr], entry, self)
|
||||
entry.__dict__[attr] = entry._state.attributes[attr]
|
||||
entry.objectclass.set(self.definition._object_class)
|
||||
for rdn in rdns: # adds virtual attributes from rdns in entry name (should be more than one with + syntax)
|
||||
if rdn[0] in entry._state.definition._attributes:
|
||||
rdn_name = entry._state.definition._attributes[rdn[0]].name # normalize case folding
|
||||
if rdn_name not in entry._state.attributes:
|
||||
entry._state.attributes[rdn_name] = self.attribute_class(entry._state.definition[rdn_name], entry, self)
|
||||
entry.__dict__[rdn_name] = entry._state.attributes[rdn_name]
|
||||
entry.__dict__[rdn_name].set(rdn[1])
|
||||
else:
|
||||
error_message = 'rdn type \'%s\' not in object class definition' % rdn[0]
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
entry._state.set_status(STATUS_VIRTUAL) # set intial status
|
||||
entry._state.set_status(STATUS_PENDING_CHANGES) # tries to change status to PENDING_CHANGES. If mandatory attributes are missing status is reverted to MANDATORY_MISSING
|
||||
self.entries.append(entry)
|
||||
return entry
|
||||
|
||||
def refresh_entry(self, entry, tries=4, seconds=2):
|
||||
conf_operational_attribute_prefix = get_config_parameter('ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX')
|
||||
|
||||
self._do_not_reset = True
|
||||
attr_list = []
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'refreshing entry <%s> for <%s>', entry, self)
|
||||
for attr in entry._state.attributes: # check friendly attribute name in AttrDef, do not check operational attributes
|
||||
if attr.lower().startswith(conf_operational_attribute_prefix.lower()):
|
||||
continue
|
||||
if entry._state.definition[attr].name:
|
||||
attr_list.append(entry._state.definition[attr].name)
|
||||
else:
|
||||
attr_list.append(entry._state.definition[attr].key)
|
||||
|
||||
temp_entry = self._refresh_object(entry.entry_dn, attr_list, tries, seconds=seconds) # if any attributes is added adds only to the entry not to the definition
|
||||
self._do_not_reset = False
|
||||
if temp_entry:
|
||||
temp_entry._state.origin = entry._state.origin
|
||||
entry.__dict__.clear()
|
||||
entry.__dict__['_state'] = temp_entry._state
|
||||
for attr in entry._state.attributes: # returns the attribute key
|
||||
entry.__dict__[attr] = entry._state.attributes[attr]
|
||||
|
||||
for attr in entry.entry_attributes: # if any attribute of the class was deleted makes it virtual
|
||||
if attr not in entry._state.attributes and attr in entry.entry_definition._attributes:
|
||||
entry._state.attributes[attr] = WritableAttribute(entry.entry_definition[attr], entry, self)
|
||||
entry.__dict__[attr] = entry._state.attributes[attr]
|
||||
entry._state.set_status(entry._state._initial_status)
|
||||
return True
|
||||
return False
|
671
thesisenv/lib/python3.6/site-packages/ldap3/abstract/entry.py
Normal file
671
thesisenv/lib/python3.6/site-packages/ldap3/abstract/entry.py
Normal file
@ -0,0 +1,671 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.08.19
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import json
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ..utils.ordDict import OrderedDict # for Python 2.6
|
||||
|
||||
from os import linesep
|
||||
|
||||
from .. import STRING_TYPES, SEQUENCE_TYPES, MODIFY_ADD, MODIFY_REPLACE
|
||||
from .attribute import WritableAttribute
|
||||
from .objectDef import ObjectDef
|
||||
from .attrDef import AttrDef
|
||||
from ..core.exceptions import LDAPKeyError, LDAPCursorError
|
||||
from ..utils.conv import check_json_dict, format_json, prepare_for_stream
|
||||
from ..protocol.rfc2849 import operation_to_ldif, add_ldif_header
|
||||
from ..utils.dn import safe_dn, safe_rdn, to_dn
|
||||
from ..utils.repr import to_stdout_encoding
|
||||
from ..utils.ciDict import CaseInsensitiveWithAliasDict
|
||||
from ..utils.config import get_config_parameter
|
||||
from . import STATUS_VIRTUAL, STATUS_WRITABLE, STATUS_PENDING_CHANGES, STATUS_COMMITTED, STATUS_DELETED,\
|
||||
STATUS_INIT, STATUS_READY_FOR_DELETION, STATUS_READY_FOR_MOVING, STATUS_READY_FOR_RENAMING, STATUS_MANDATORY_MISSING, STATUSES, INITIAL_STATUSES
|
||||
from ..core.results import RESULT_SUCCESS
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
|
||||
|
||||
|
||||
class EntryState(object):
|
||||
"""Contains data on the status of the entry. Does not pollute the Entry __dict__.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dn, cursor):
|
||||
self.dn = dn
|
||||
self._initial_status = None
|
||||
self._to = None # used for move and rename
|
||||
self.status = STATUS_INIT
|
||||
self.attributes = CaseInsensitiveWithAliasDict()
|
||||
self.raw_attributes = CaseInsensitiveWithAliasDict()
|
||||
self.response = None
|
||||
self.cursor = cursor
|
||||
self.origin = None # reference to the original read-only entry (set when made writable). Needed to update attributes in read-only when modified (only if both refer the same server)
|
||||
self.read_time = None
|
||||
self.changes = OrderedDict() # includes changes to commit in a writable entry
|
||||
if cursor.definition:
|
||||
self.definition = cursor.definition
|
||||
else:
|
||||
self.definition = None
|
||||
|
||||
def __repr__(self):
|
||||
if self.__dict__ and self.dn is not None:
|
||||
r = 'DN: ' + to_stdout_encoding(self.dn) + ' - STATUS: ' + ((self._initial_status + ', ') if self._initial_status != self.status else '') + self.status + ' - READ TIME: ' + (self.read_time.isoformat() if self.read_time else '<never>') + linesep
|
||||
r += 'attributes: ' + ', '.join(sorted(self.attributes.keys())) + linesep
|
||||
r += 'object def: ' + (', '.join(sorted(self.definition._object_class)) if self.definition._object_class else '<None>') + linesep
|
||||
r += 'attr defs: ' + ', '.join(sorted(self.definition._attributes.keys())) + linesep
|
||||
r += 'response: ' + ('present' if self.response else '<None>') + linesep
|
||||
r += 'cursor: ' + (self.cursor.__class__.__name__ if self.cursor else '<None>') + linesep
|
||||
return r
|
||||
else:
|
||||
return object.__repr__(self)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def set_status(self, status):
|
||||
conf_ignored_mandatory_attributes_in_object_def = [v.lower() for v in get_config_parameter('IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF')]
|
||||
if status not in STATUSES:
|
||||
error_message = 'invalid entry status ' + str(status)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
if status in INITIAL_STATUSES:
|
||||
self._initial_status = status
|
||||
self.status = status
|
||||
if status == STATUS_DELETED:
|
||||
self._initial_status = STATUS_VIRTUAL
|
||||
if status == STATUS_COMMITTED:
|
||||
self._initial_status = STATUS_WRITABLE
|
||||
if self.status == STATUS_VIRTUAL or (self.status == STATUS_PENDING_CHANGES and self._initial_status == STATUS_VIRTUAL): # checks if all mandatory attributes are present in new entries
|
||||
for attr in self.definition._attributes:
|
||||
if self.definition._attributes[attr].mandatory and attr.lower() not in conf_ignored_mandatory_attributes_in_object_def:
|
||||
if (attr not in self.attributes or self.attributes[attr].virtual) and attr not in self.changes:
|
||||
self.status = STATUS_MANDATORY_MISSING
|
||||
break
|
||||
|
||||
|
||||
class EntryBase(object):
|
||||
"""The Entry object contains a single LDAP entry.
|
||||
Attributes can be accessed either by sequence, by assignment
|
||||
or as dictionary keys. Keys are not case sensitive.
|
||||
|
||||
The Entry object is read only
|
||||
|
||||
- The DN is retrieved by _dn
|
||||
- The cursor reference is in _cursor
|
||||
- Raw attributes values are retrieved with _raw_attributes and the _raw_attribute() methods
|
||||
"""
|
||||
|
||||
def __init__(self, dn, cursor):
|
||||
self.__dict__['_state'] = EntryState(dn, cursor)
|
||||
|
||||
def __repr__(self):
|
||||
if self.__dict__ and self.entry_dn is not None:
|
||||
r = 'DN: ' + to_stdout_encoding(self.entry_dn) + ' - STATUS: ' + ((self._state._initial_status + ', ') if self._state._initial_status != self.entry_status else '') + self.entry_status + ' - READ TIME: ' + (self.entry_read_time.isoformat() if self.entry_read_time else '<never>') + linesep
|
||||
if self._state.attributes:
|
||||
for attr in sorted(self._state.attributes):
|
||||
if self._state.attributes[attr] or (hasattr(self._state.attributes[attr], 'changes') and self._state.attributes[attr].changes):
|
||||
r += ' ' + repr(self._state.attributes[attr]) + linesep
|
||||
return r
|
||||
else:
|
||||
return object.__repr__(self)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __iter__(self):
|
||||
for attribute in self._state.attributes:
|
||||
yield self._state.attributes[attribute]
|
||||
# raise StopIteration # deprecated in PEP 479
|
||||
return
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self.__getitem__(item)
|
||||
return True
|
||||
except LDAPKeyError:
|
||||
return False
|
||||
|
||||
def __getattr__(self, item):
|
||||
if isinstance(item, STRING_TYPES):
|
||||
if item == '_state':
|
||||
return self.__dict__['_state']
|
||||
item = ''.join(item.split()).lower()
|
||||
attr_found = None
|
||||
for attr in self._state.attributes.keys():
|
||||
if item == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.keys():
|
||||
if item + ';binary' == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item + ';binary' == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.keys():
|
||||
if item + ';range' in attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item + ';range' in attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
error_message = 'attribute \'%s\' not found' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
return self._state.attributes[attr]
|
||||
error_message = 'attribute name must be a string'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
if item in self._state.attributes:
|
||||
error_message = 'attribute \'%s\' is read only' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
else:
|
||||
error_message = 'entry is read only, cannot add \'%s\'' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
def __getitem__(self, item):
|
||||
if isinstance(item, STRING_TYPES):
|
||||
item = ''.join(item.split()).lower()
|
||||
attr_found = None
|
||||
for attr in self._state.attributes.keys():
|
||||
if item == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.keys():
|
||||
if item + ';binary' == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item + ';binary' == attr.lower():
|
||||
attr_found = attr
|
||||
break
|
||||
if not attr_found:
|
||||
error_message = 'key \'%s\' not found' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
return self._state.attributes[attr]
|
||||
|
||||
error_message = 'key must be a string'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, EntryBase):
|
||||
return self.entry_dn == other.entry_dn
|
||||
|
||||
return False
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, EntryBase):
|
||||
return self.entry_dn <= other.entry_dn
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def entry_dn(self):
|
||||
return self._state.dn
|
||||
|
||||
@property
|
||||
def entry_cursor(self):
|
||||
return self._state.cursor
|
||||
|
||||
@property
|
||||
def entry_status(self):
|
||||
return self._state.status
|
||||
|
||||
@property
|
||||
def entry_definition(self):
|
||||
return self._state.definition
|
||||
|
||||
@property
|
||||
def entry_raw_attributes(self):
|
||||
return self._state.entry_raw_attributes
|
||||
|
||||
def entry_raw_attribute(self, name):
|
||||
"""
|
||||
|
||||
:param name: name of the attribute
|
||||
:return: raw (unencoded) value of the attribute, None if attribute is not found
|
||||
"""
|
||||
return self._state.entry_raw_attributes[name] if name in self._state.entry_raw_attributes else None
|
||||
|
||||
@property
|
||||
def entry_mandatory_attributes(self):
|
||||
return [attribute for attribute in self.entry_definition._attributes if self.entry_definition._attributes[attribute].mandatory]
|
||||
|
||||
@property
|
||||
def entry_attributes(self):
|
||||
return list(self._state.attributes.keys())
|
||||
|
||||
@property
|
||||
def entry_attributes_as_dict(self):
|
||||
return dict((attribute_key, attribute_value.values) for (attribute_key, attribute_value) in self._state.attributes.items())
|
||||
|
||||
@property
|
||||
def entry_read_time(self):
|
||||
return self._state.read_time
|
||||
|
||||
@property
|
||||
def _changes(self):
|
||||
return self._state.changes
|
||||
|
||||
def entry_to_json(self, raw=False, indent=4, sort=True, stream=None, checked_attributes=True, include_empty=True):
|
||||
json_entry = dict()
|
||||
json_entry['dn'] = self.entry_dn
|
||||
if checked_attributes:
|
||||
if not include_empty:
|
||||
# needed for python 2.6 compatibility
|
||||
json_entry['attributes'] = dict((key, self.entry_attributes_as_dict[key]) for key in self.entry_attributes_as_dict if self.entry_attributes_as_dict[key])
|
||||
else:
|
||||
json_entry['attributes'] = self.entry_attributes_as_dict
|
||||
if raw:
|
||||
if not include_empty:
|
||||
# needed for python 2.6 compatibility
|
||||
json_entry['raw'] = dict((key, self.entry_raw_attributes[key]) for key in self.entry_raw_attributes if self.entry_raw_attributes[key])
|
||||
else:
|
||||
json_entry['raw'] = dict(self.entry_raw_attributes)
|
||||
|
||||
if str is bytes: # Python 2
|
||||
check_json_dict(json_entry)
|
||||
|
||||
json_output = json.dumps(json_entry,
|
||||
ensure_ascii=True,
|
||||
sort_keys=sort,
|
||||
indent=indent,
|
||||
check_circular=True,
|
||||
default=format_json,
|
||||
separators=(',', ': '))
|
||||
|
||||
if stream:
|
||||
stream.write(json_output)
|
||||
|
||||
return json_output
|
||||
|
||||
def entry_to_ldif(self, all_base64=False, line_separator=None, sort_order=None, stream=None):
|
||||
ldif_lines = operation_to_ldif('searchResponse', [self._state.response], all_base64, sort_order=sort_order)
|
||||
ldif_lines = add_ldif_header(ldif_lines)
|
||||
line_separator = line_separator or linesep
|
||||
ldif_output = line_separator.join(ldif_lines)
|
||||
if stream:
|
||||
if stream.tell() == 0:
|
||||
header = add_ldif_header(['-'])[0]
|
||||
stream.write(prepare_for_stream(header + line_separator + line_separator))
|
||||
stream.write(prepare_for_stream(ldif_output + line_separator + line_separator))
|
||||
return ldif_output
|
||||
|
||||
|
||||
class Entry(EntryBase):
|
||||
"""The Entry object contains a single LDAP entry.
|
||||
Attributes can be accessed either by sequence, by assignment
|
||||
or as dictionary keys. Keys are not case sensitive.
|
||||
|
||||
The Entry object is read only
|
||||
|
||||
- The DN is retrieved by _dn()
|
||||
- The Reader reference is in _cursor()
|
||||
- Raw attributes values are retrieved by the _ra_attributes and
|
||||
_raw_attribute() methods
|
||||
|
||||
"""
|
||||
def entry_writable(self, object_def=None, writer_cursor=None, attributes=None, custom_validator=None, auxiliary_class=None):
|
||||
if not self.entry_cursor.schema:
|
||||
error_message = 'schema must be available to make an entry writable'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
# returns a new WritableEntry and its Writer cursor
|
||||
if object_def is None:
|
||||
if self.entry_cursor.definition._object_class:
|
||||
object_def = self.entry_definition._object_class
|
||||
auxiliary_class = self.entry_definition._auxiliary_class + (auxiliary_class if isinstance(auxiliary_class, SEQUENCE_TYPES) else [])
|
||||
elif 'objectclass' in self:
|
||||
object_def = self.objectclass.values
|
||||
|
||||
if not object_def:
|
||||
error_message = 'object class must be specified to make an entry writable'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
if not isinstance(object_def, ObjectDef):
|
||||
object_def = ObjectDef(object_def, self.entry_cursor.schema, custom_validator, auxiliary_class)
|
||||
|
||||
if attributes:
|
||||
if isinstance(attributes, STRING_TYPES):
|
||||
attributes = [attributes]
|
||||
|
||||
if isinstance(attributes, SEQUENCE_TYPES):
|
||||
for attribute in attributes:
|
||||
if attribute not in object_def._attributes:
|
||||
error_message = 'attribute \'%s\' not in schema for \'%s\'' % (attribute, object_def)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
else:
|
||||
attributes = []
|
||||
|
||||
if not writer_cursor:
|
||||
from .cursor import Writer # local import to avoid circular reference in import at startup
|
||||
writable_cursor = Writer(self.entry_cursor.connection, object_def)
|
||||
else:
|
||||
writable_cursor = writer_cursor
|
||||
|
||||
if attributes: # force reading of attributes
|
||||
writable_entry = writable_cursor._refresh_object(self.entry_dn, list(attributes) + self.entry_attributes)
|
||||
else:
|
||||
writable_entry = writable_cursor._create_entry(self._state.response)
|
||||
writable_cursor.entries.append(writable_entry)
|
||||
writable_entry._state.read_time = self.entry_read_time
|
||||
writable_entry._state.origin = self # reference to the original read-only entry
|
||||
# checks original entry for custom definitions in AttrDefs
|
||||
for attr in writable_entry._state.origin.entry_definition._attributes:
|
||||
original_attr = writable_entry._state.origin.entry_definition._attributes[attr]
|
||||
if attr != original_attr.name and attr not in writable_entry._state.attributes:
|
||||
old_attr_def = writable_entry.entry_definition._attributes[original_attr.name]
|
||||
new_attr_def = AttrDef(original_attr.name,
|
||||
key=attr,
|
||||
validate=original_attr.validate,
|
||||
pre_query=original_attr.pre_query,
|
||||
post_query=original_attr.post_query,
|
||||
default=original_attr.default,
|
||||
dereference_dn=original_attr.dereference_dn,
|
||||
description=original_attr.description,
|
||||
mandatory=old_attr_def.mandatory, # keeps value read from schema
|
||||
single_value=old_attr_def.single_value, # keeps value read from schema
|
||||
alias=original_attr.other_names)
|
||||
object_def = writable_entry.entry_definition
|
||||
object_def -= old_attr_def
|
||||
object_def += new_attr_def
|
||||
# updates attribute name in entry attributes
|
||||
new_attr = WritableAttribute(new_attr_def, writable_entry, writable_cursor)
|
||||
if original_attr.name in writable_entry._state.attributes:
|
||||
new_attr.other_names = writable_entry._state.attributes[original_attr.name].other_names
|
||||
new_attr.raw_values = writable_entry._state.attributes[original_attr.name].raw_values
|
||||
new_attr.values = writable_entry._state.attributes[original_attr.name].values
|
||||
new_attr.response = writable_entry._state.attributes[original_attr.name].response
|
||||
writable_entry._state.attributes[attr] = new_attr
|
||||
# writable_entry._state.attributes.set_alias(attr, new_attr.other_names)
|
||||
del writable_entry._state.attributes[original_attr.name]
|
||||
|
||||
writable_entry._state.set_status(STATUS_WRITABLE)
|
||||
return writable_entry
|
||||
|
||||
|
||||
class WritableEntry(EntryBase):
|
||||
def __setitem__(self, key, value):
|
||||
if value is not Ellipsis: # hack for using implicit operators in writable attributes
|
||||
self.__setattr__(key, value)
|
||||
|
||||
def __setattr__(self, item, value):
|
||||
conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
|
||||
if item == '_state' and isinstance(value, EntryState):
|
||||
self.__dict__['_state'] = value
|
||||
return
|
||||
|
||||
if value is not Ellipsis: # hack for using implicit operators in writable attributes
|
||||
# checks if using an alias
|
||||
if item in self.entry_cursor.definition._attributes or item.lower() in conf_attributes_excluded_from_object_def:
|
||||
if item not in self._state.attributes: # setting value to an attribute still without values
|
||||
new_attribute = WritableAttribute(self.entry_cursor.definition._attributes[item], self, cursor=self.entry_cursor)
|
||||
self._state.attributes[str(item)] = new_attribute # force item to a string for key in attributes dict
|
||||
self._state.attributes[item].set(value) # try to add to new_values
|
||||
else:
|
||||
error_message = 'attribute \'%s\' not defined' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if isinstance(item, STRING_TYPES):
|
||||
if item == '_state':
|
||||
return self.__dict__['_state']
|
||||
item = ''.join(item.split()).lower()
|
||||
for attr in self._state.attributes.keys():
|
||||
if item == attr.lower():
|
||||
return self._state.attributes[attr]
|
||||
for attr in self._state.attributes.aliases():
|
||||
if item == attr.lower():
|
||||
return self._state.attributes[attr]
|
||||
if item in self.entry_definition._attributes: # item is a new attribute to commit, creates the AttrDef and add to the attributes to retrive
|
||||
self._state.attributes[item] = WritableAttribute(self.entry_definition._attributes[item], self, self.entry_cursor)
|
||||
self.entry_cursor.attributes.add(item)
|
||||
return self._state.attributes[item]
|
||||
error_message = 'attribute \'%s\' not defined' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
else:
|
||||
error_message = 'attribute name must be a string'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
|
||||
@property
|
||||
def entry_virtual_attributes(self):
|
||||
return [attr for attr in self.entry_attributes if self[attr].virtual]
|
||||
|
||||
def entry_commit_changes(self, refresh=True, controls=None, clear_history=True):
|
||||
if clear_history:
|
||||
self.entry_cursor._reset_history()
|
||||
|
||||
if self.entry_status == STATUS_READY_FOR_DELETION:
|
||||
result = self.entry_cursor.connection.delete(self.entry_dn, controls)
|
||||
if not self.entry_cursor.connection.strategy.sync:
|
||||
response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.entry_cursor.connection.response
|
||||
result = self.entry_cursor.connection.result
|
||||
request = self.entry_cursor.connection.request
|
||||
self.entry_cursor._store_operation_in_history(request, result, response)
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
dn = self.entry_dn
|
||||
if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # deletes original read-only Entry
|
||||
cursor = self._state.origin.entry_cursor
|
||||
self._state.origin.__dict__.clear()
|
||||
self._state.origin.__dict__['_state'] = EntryState(dn, cursor)
|
||||
self._state.origin._state.set_status(STATUS_DELETED)
|
||||
cursor = self.entry_cursor
|
||||
self.__dict__.clear()
|
||||
self._state = EntryState(dn, cursor)
|
||||
self._state.set_status(STATUS_DELETED)
|
||||
return True
|
||||
return False
|
||||
elif self.entry_status == STATUS_READY_FOR_MOVING:
|
||||
result = self.entry_cursor.connection.modify_dn(self.entry_dn, '+'.join(safe_rdn(self.entry_dn)), new_superior=self._state._to)
|
||||
if not self.entry_cursor.connection.strategy.sync:
|
||||
response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.entry_cursor.connection.response
|
||||
result = self.entry_cursor.connection.result
|
||||
request = self.entry_cursor.connection.request
|
||||
self.entry_cursor._store_operation_in_history(request, result, response)
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
self._state.dn = safe_dn('+'.join(safe_rdn(self.entry_dn)) + ',' + self._state._to)
|
||||
if refresh:
|
||||
if self.entry_refresh():
|
||||
if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # refresh dn of origin
|
||||
self._state.origin._state.dn = self.entry_dn
|
||||
self._state.set_status(STATUS_COMMITTED)
|
||||
self._state._to = None
|
||||
return True
|
||||
return False
|
||||
elif self.entry_status == STATUS_READY_FOR_RENAMING:
|
||||
rdn = '+'.join(safe_rdn(self._state._to))
|
||||
result = self.entry_cursor.connection.modify_dn(self.entry_dn, rdn)
|
||||
if not self.entry_cursor.connection.strategy.sync:
|
||||
response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.entry_cursor.connection.response
|
||||
result = self.entry_cursor.connection.result
|
||||
request = self.entry_cursor.connection.request
|
||||
self.entry_cursor._store_operation_in_history(request, result, response)
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
self._state.dn = rdn + ',' + ','.join(to_dn(self.entry_dn)[1:])
|
||||
if refresh:
|
||||
if self.entry_refresh():
|
||||
if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # refresh dn of origin
|
||||
self._state.origin._state.dn = self.entry_dn
|
||||
self._state.set_status(STATUS_COMMITTED)
|
||||
self._state._to = None
|
||||
return True
|
||||
return False
|
||||
elif self.entry_status in [STATUS_VIRTUAL, STATUS_MANDATORY_MISSING]:
|
||||
missing_attributes = []
|
||||
for attr in self.entry_mandatory_attributes:
|
||||
if (attr not in self._state.attributes or self._state.attributes[attr].virtual) and attr not in self._changes:
|
||||
missing_attributes.append('\'' + attr + '\'')
|
||||
error_message = 'mandatory attributes %s missing in entry %s' % (', '.join(missing_attributes), self.entry_dn)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
elif self.entry_status == STATUS_PENDING_CHANGES:
|
||||
if self._changes:
|
||||
if self.entry_definition._auxiliary_class: # checks if an attribute is from an auxiliary class and adds it to the objectClass attribute if not present
|
||||
for attr in self._changes:
|
||||
# checks schema to see if attribute is defined in one of the already present object classes
|
||||
attr_classes = self.entry_cursor.schema.attribute_types[attr].mandatory_in + self.entry_cursor.schema.attribute_types[attr].optional_in
|
||||
for object_class in self.objectclass:
|
||||
if object_class in attr_classes:
|
||||
break
|
||||
else: # executed only if the attribute class is not present in the objectClass attribute
|
||||
# checks if attribute is defined in one of the possible auxiliary classes
|
||||
for aux_class in self.entry_definition._auxiliary_class:
|
||||
if aux_class in attr_classes:
|
||||
if self._state._initial_status == STATUS_VIRTUAL: # entry is new, there must be a pending objectClass MODIFY_REPLACE
|
||||
self._changes['objectClass'][0][1].append(aux_class)
|
||||
else:
|
||||
self.objectclass += aux_class
|
||||
if self._state._initial_status == STATUS_VIRTUAL:
|
||||
new_attributes = dict()
|
||||
for attr in self._changes:
|
||||
new_attributes[attr] = self._changes[attr][0][1]
|
||||
result = self.entry_cursor.connection.add(self.entry_dn, None, new_attributes, controls)
|
||||
else:
|
||||
result = self.entry_cursor.connection.modify(self.entry_dn, self._changes, controls)
|
||||
|
||||
if not self.entry_cursor.connection.strategy.sync: # asynchronous request
|
||||
response, result, request = self.entry_cursor.connection.get_response(result, get_request=True)
|
||||
else:
|
||||
response = self.entry_cursor.connection.response
|
||||
result = self.entry_cursor.connection.result
|
||||
request = self.entry_cursor.connection.request
|
||||
self.entry_cursor._store_operation_in_history(request, result, response)
|
||||
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
if refresh:
|
||||
if self.entry_refresh():
|
||||
if self._state.origin and self.entry_cursor.connection.server == self._state.origin.entry_cursor.connection.server: # updates original read-only entry if present
|
||||
for attr in self: # adds AttrDefs from writable entry to origin entry definition if some is missing
|
||||
if attr.key in self.entry_definition._attributes and attr.key not in self._state.origin.entry_definition._attributes:
|
||||
self._state.origin.entry_cursor.definition.add_attribute(self.entry_cursor.definition._attributes[attr.key]) # adds AttrDef from writable entry to original entry if missing
|
||||
temp_entry = self._state.origin.entry_cursor._create_entry(self._state.response)
|
||||
self._state.origin.__dict__.clear()
|
||||
self._state.origin.__dict__['_state'] = temp_entry._state
|
||||
for attr in self: # returns the whole attribute object
|
||||
if not attr.virtual:
|
||||
self._state.origin.__dict__[attr.key] = self._state.origin._state.attributes[attr.key]
|
||||
self._state.origin._state.read_time = self.entry_read_time
|
||||
else:
|
||||
self.entry_discard_changes() # if not refreshed remove committed changes
|
||||
self._state.set_status(STATUS_COMMITTED)
|
||||
return True
|
||||
return False
|
||||
|
||||
def entry_discard_changes(self):
|
||||
self._changes.clear()
|
||||
self._state.set_status(self._state._initial_status)
|
||||
|
||||
def entry_delete(self):
|
||||
if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_DELETION]:
|
||||
error_message = 'cannot delete entry, invalid status: ' + self.entry_status
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self._state.set_status(STATUS_READY_FOR_DELETION)
|
||||
|
||||
def entry_refresh(self, tries=4, seconds=2):
|
||||
"""
|
||||
|
||||
Refreshes the entry from the LDAP Server
|
||||
"""
|
||||
if self.entry_cursor.connection:
|
||||
if self.entry_cursor.refresh_entry(self, tries, seconds):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def entry_move(self, destination_dn):
|
||||
if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_MOVING]:
|
||||
error_message = 'cannot move entry, invalid status: ' + self.entry_status
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self._state._to = safe_dn(destination_dn)
|
||||
self._state.set_status(STATUS_READY_FOR_MOVING)
|
||||
|
||||
def entry_rename(self, new_name):
|
||||
if self.entry_status not in [STATUS_WRITABLE, STATUS_COMMITTED, STATUS_READY_FOR_RENAMING]:
|
||||
error_message = 'cannot rename entry, invalid status: ' + self.entry_status
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPCursorError(error_message)
|
||||
self._state._to = new_name
|
||||
self._state.set_status(STATUS_READY_FOR_RENAMING)
|
||||
|
||||
@property
|
||||
def entry_changes(self):
|
||||
return self._changes
|
@ -0,0 +1,270 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.02.02
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import linesep
|
||||
|
||||
from .attrDef import AttrDef
|
||||
from ..core.exceptions import LDAPKeyError, LDAPObjectError, LDAPAttributeError, LDAPSchemaError
|
||||
from .. import STRING_TYPES, SEQUENCE_TYPES, Server, Connection
|
||||
from ..protocol.rfc4512 import SchemaInfo, constant_to_class_kind
|
||||
from ..protocol.formatters.standard import find_attribute_validator
|
||||
from ..utils.ciDict import CaseInsensitiveWithAliasDict
|
||||
from ..utils.config import get_config_parameter
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, EXTENDED
|
||||
|
||||
|
||||
class ObjectDef(object):
|
||||
"""Represent an object in the LDAP server. AttrDefs are stored in a dictionary; the key is the friendly name defined in AttrDef.
|
||||
|
||||
AttrDefs can be added and removed using the += ad -= operators
|
||||
|
||||
ObjectDef can be accessed either as a sequence and a dictionary. When accessed the whole AttrDef instance is returned
|
||||
|
||||
"""
|
||||
def __init__(self, object_class=None, schema=None, custom_validator=None, auxiliary_class=None):
|
||||
if object_class is None:
|
||||
object_class = []
|
||||
|
||||
if not isinstance(object_class, SEQUENCE_TYPES):
|
||||
object_class = [object_class]
|
||||
|
||||
if auxiliary_class is None:
|
||||
auxiliary_class = []
|
||||
|
||||
if not isinstance(auxiliary_class, SEQUENCE_TYPES):
|
||||
auxiliary_class = [auxiliary_class]
|
||||
|
||||
self.__dict__['_attributes'] = CaseInsensitiveWithAliasDict()
|
||||
self.__dict__['_custom_validator'] = custom_validator
|
||||
self.__dict__['_oid_info'] = []
|
||||
|
||||
if isinstance(schema, Connection) and (schema._deferred_bind or schema._deferred_open): # probably a lazy connection, tries to bind
|
||||
schema._fire_deferred()
|
||||
|
||||
if schema is not None:
|
||||
if isinstance(schema, Server):
|
||||
schema = schema.schema
|
||||
elif isinstance(schema, Connection):
|
||||
schema = schema.server.schema
|
||||
elif isinstance(schema, SchemaInfo):
|
||||
pass
|
||||
elif schema:
|
||||
error_message = 'unable to read schema'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPSchemaError(error_message)
|
||||
if schema is None:
|
||||
error_message = 'schema not present'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPSchemaError(error_message)
|
||||
self.__dict__['_schema'] = schema
|
||||
|
||||
if self._schema:
|
||||
object_class = [schema.object_classes[name].name[0] for name in object_class] # uses object class names capitalized as in schema
|
||||
auxiliary_class = [schema.object_classes[name].name[0] for name in auxiliary_class]
|
||||
for object_name in object_class:
|
||||
if object_name:
|
||||
self._populate_attr_defs(object_name)
|
||||
|
||||
for object_name in auxiliary_class:
|
||||
if object_name:
|
||||
self._populate_attr_defs(object_name)
|
||||
|
||||
self.__dict__['_object_class'] = object_class
|
||||
self.__dict__['_auxiliary_class'] = auxiliary_class
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated ObjectDef: <%r>', self)
|
||||
|
||||
def _populate_attr_defs(self, object_name):
|
||||
if object_name in self._schema.object_classes:
|
||||
object_schema = self._schema.object_classes[object_name]
|
||||
self.__dict__['_oid_info'].append(object_name + " (" + constant_to_class_kind(object_schema.kind) + ") " + str(object_schema.oid))
|
||||
|
||||
if object_schema.superior:
|
||||
for sup in object_schema.superior:
|
||||
self._populate_attr_defs(sup)
|
||||
for attribute_name in object_schema.must_contain:
|
||||
self.add_from_schema(attribute_name, True)
|
||||
for attribute_name in object_schema.may_contain:
|
||||
if attribute_name not in self._attributes: # the attribute could already be defined as "mandatory" in a superclass
|
||||
self.add_from_schema(attribute_name, False)
|
||||
else:
|
||||
error_message = 'object class \'%s\' not defined in schema' % object_name
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPObjectError(error_message)
|
||||
|
||||
def __repr__(self):
|
||||
if self._object_class:
|
||||
r = 'OBJ : ' + ', '.join(self._object_class) + linesep
|
||||
else:
|
||||
r = 'OBJ : <None>' + linesep
|
||||
if self._auxiliary_class:
|
||||
r += 'AUX : ' + ', '.join(self._auxiliary_class) + linesep
|
||||
else:
|
||||
r += 'AUX : <None>' + linesep
|
||||
r += 'OID: ' + ', '.join([oid for oid in self._oid_info]) + linesep
|
||||
r += 'MUST: ' + ', '.join(sorted([attr for attr in self._attributes if self._attributes[attr].mandatory])) + linesep
|
||||
r += 'MAY : ' + ', '.join(sorted([attr for attr in self._attributes if not self._attributes[attr].mandatory])) + linesep
|
||||
|
||||
return r
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__getattr__(item)
|
||||
|
||||
def __getattr__(self, item):
|
||||
item = ''.join(item.split()).lower()
|
||||
if '_attributes' in self.__dict__:
|
||||
try:
|
||||
return self._attributes[item]
|
||||
except KeyError:
|
||||
error_message = 'key \'%s\' not present' % item
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
else:
|
||||
error_message = 'internal _attributes property not defined'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
error_message = 'object \'%s\' is read only' % key
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPObjectError(error_message)
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.add_attribute(other)
|
||||
return self
|
||||
|
||||
def __isub__(self, other):
|
||||
if isinstance(other, AttrDef):
|
||||
self.remove_attribute(other.key)
|
||||
elif isinstance(other, STRING_TYPES):
|
||||
self.remove_attribute(other)
|
||||
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
for attribute in self._attributes:
|
||||
yield self._attributes[attribute]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._attributes)
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
def __bool__(self): # needed to make the objectDef appears as existing in "if cursor:" even if there are no entries
|
||||
return True
|
||||
else: # Python 2
|
||||
def __nonzero__(self):
|
||||
return True
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self.__getitem__(item)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add_from_schema(self, attribute_name, mandatory=False):
|
||||
attr_def = AttrDef(attribute_name)
|
||||
attr_def.validate = find_attribute_validator(self._schema, attribute_name, self._custom_validator)
|
||||
attr_def.mandatory = mandatory # in schema mandatory is specified in the object class, not in the attribute class
|
||||
if self._schema and self._schema.attribute_types and attribute_name in self._schema.attribute_types:
|
||||
attr_def.single_value = self._schema.attribute_types[attribute_name].single_value
|
||||
attr_def.oid_info = self._schema.attribute_types[attribute_name]
|
||||
self.add_attribute(attr_def)
|
||||
|
||||
def add_attribute(self, definition=None):
|
||||
"""Add an AttrDef to the ObjectDef. Can be called with the += operator.
|
||||
:param definition: the AttrDef object to add, can also be a string containing the name of attribute to add. Can be a list of both
|
||||
|
||||
"""
|
||||
conf_attributes_excluded_from_object_def = [v.lower() for v in get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF')]
|
||||
if isinstance(definition, STRING_TYPES):
|
||||
self.add_from_schema(definition)
|
||||
elif isinstance(definition, AttrDef):
|
||||
if definition.key.lower() not in conf_attributes_excluded_from_object_def:
|
||||
if definition.key not in self._attributes:
|
||||
self._attributes[definition.key] = definition
|
||||
if definition.name and definition.name != definition.key:
|
||||
self._attributes.set_alias(definition.key, definition.name)
|
||||
other_names = [name for name in definition.oid_info.name if definition.key.lower() != name.lower()] if definition.oid_info else None
|
||||
if other_names:
|
||||
self._attributes.set_alias(definition.key, other_names)
|
||||
|
||||
if not definition.validate:
|
||||
validator = find_attribute_validator(self._schema, definition.key, self._custom_validator)
|
||||
self._attributes[definition.key].validate = validator
|
||||
elif isinstance(definition, SEQUENCE_TYPES):
|
||||
for element in definition:
|
||||
self.add_attribute(element)
|
||||
else:
|
||||
error_message = 'unable to add element to object definition'
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPObjectError(error_message)
|
||||
|
||||
def remove_attribute(self, item):
|
||||
"""Remove an AttrDef from the ObjectDef. Can be called with the -= operator.
|
||||
:param item: the AttrDef to remove, can also be a string containing the name of attribute to remove
|
||||
|
||||
"""
|
||||
key = None
|
||||
if isinstance(item, STRING_TYPES):
|
||||
key = ''.join(item.split()).lower()
|
||||
elif isinstance(item, AttrDef):
|
||||
key = item.key.lower()
|
||||
|
||||
if key:
|
||||
for attr in self._attributes:
|
||||
if key == attr.lower():
|
||||
del self._attributes[attr]
|
||||
break
|
||||
else:
|
||||
error_message = 'key \'%s\' not present' % key
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPKeyError(error_message)
|
||||
else:
|
||||
error_message = 'key type must be str or AttrDef not ' + str(type(item))
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', error_message, self)
|
||||
raise LDAPAttributeError(error_message)
|
||||
|
||||
def clear_attributes(self):
|
||||
"""Empty the ObjectDef attribute list
|
||||
|
||||
"""
|
||||
self.__dict__['object_class'] = None
|
||||
self.__dict__['auxiliary_class'] = None
|
||||
self.__dict__['_attributes'] = dict()
|
1501
thesisenv/lib/python3.6/site-packages/ldap3/core/connection.py
Normal file
1501
thesisenv/lib/python3.6/site-packages/ldap3/core/connection.py
Normal file
File diff suppressed because it is too large
Load Diff
597
thesisenv/lib/python3.6/site-packages/ldap3/core/exceptions.py
Normal file
597
thesisenv/lib/python3.6/site-packages/ldap3/core/exceptions.py
Normal file
@ -0,0 +1,597 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.05.14
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import sep
|
||||
from .results import RESULT_OPERATIONS_ERROR, RESULT_PROTOCOL_ERROR, RESULT_TIME_LIMIT_EXCEEDED, RESULT_SIZE_LIMIT_EXCEEDED, \
|
||||
RESULT_STRONGER_AUTH_REQUIRED, RESULT_REFERRAL, RESULT_ADMIN_LIMIT_EXCEEDED, RESULT_UNAVAILABLE_CRITICAL_EXTENSION, \
|
||||
RESULT_AUTH_METHOD_NOT_SUPPORTED, RESULT_UNDEFINED_ATTRIBUTE_TYPE, RESULT_NO_SUCH_ATTRIBUTE, \
|
||||
RESULT_SASL_BIND_IN_PROGRESS, RESULT_CONFIDENTIALITY_REQUIRED, RESULT_INAPPROPRIATE_MATCHING, \
|
||||
RESULT_CONSTRAINT_VIOLATION, \
|
||||
RESULT_ATTRIBUTE_OR_VALUE_EXISTS, RESULT_INVALID_ATTRIBUTE_SYNTAX, RESULT_NO_SUCH_OBJECT, RESULT_ALIAS_PROBLEM, \
|
||||
RESULT_INVALID_DN_SYNTAX, RESULT_ALIAS_DEREFERENCING_PROBLEM, RESULT_INVALID_CREDENTIALS, RESULT_LOOP_DETECTED, \
|
||||
RESULT_ENTRY_ALREADY_EXISTS, RESULT_LCUP_SECURITY_VIOLATION, RESULT_CANCELED, RESULT_E_SYNC_REFRESH_REQUIRED, \
|
||||
RESULT_NO_SUCH_OPERATION, RESULT_LCUP_INVALID_DATA, RESULT_OBJECT_CLASS_MODS_PROHIBITED, RESULT_NAMING_VIOLATION, \
|
||||
RESULT_INSUFFICIENT_ACCESS_RIGHTS, RESULT_OBJECT_CLASS_VIOLATION, RESULT_TOO_LATE, RESULT_CANNOT_CANCEL, \
|
||||
RESULT_LCUP_UNSUPPORTED_SCHEME, RESULT_BUSY, RESULT_AFFECT_MULTIPLE_DSAS, RESULT_UNAVAILABLE, \
|
||||
RESULT_NOT_ALLOWED_ON_NON_LEAF, \
|
||||
RESULT_UNWILLING_TO_PERFORM, RESULT_OTHER, RESULT_LCUP_RELOAD_REQUIRED, RESULT_ASSERTION_FAILED, \
|
||||
RESULT_AUTHORIZATION_DENIED, RESULT_LCUP_RESOURCES_EXHAUSTED, RESULT_NOT_ALLOWED_ON_RDN, \
|
||||
RESULT_INAPPROPRIATE_AUTHENTICATION
|
||||
import socket
|
||||
|
||||
|
||||
# LDAPException hierarchy
|
||||
class LDAPException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPOperationResult(LDAPException):
|
||||
def __new__(cls, result=None, description=None, dn=None, message=None, response_type=None, response=None):
|
||||
if cls is LDAPOperationResult and result and result in exception_table:
|
||||
exc = super(LDAPOperationResult, exception_table[result]).__new__(
|
||||
exception_table[result]) # create an exception of the required result error
|
||||
exc.result = result
|
||||
exc.description = description
|
||||
exc.dn = dn
|
||||
exc.message = message
|
||||
exc.type = response_type
|
||||
exc.response = response
|
||||
else:
|
||||
exc = super(LDAPOperationResult, cls).__new__(cls)
|
||||
return exc
|
||||
|
||||
def __init__(self, result=None, description=None, dn=None, message=None, response_type=None, response=None):
|
||||
self.result = result
|
||||
self.description = description
|
||||
self.dn = dn
|
||||
self.message = message
|
||||
self.type = response_type
|
||||
self.response = response
|
||||
|
||||
def __str__(self):
|
||||
s = [self.__class__.__name__,
|
||||
str(self.result) if self.result else None,
|
||||
self.description if self.description else None,
|
||||
self.dn if self.dn else None,
|
||||
self.message if self.message else None,
|
||||
self.type if self.type else None,
|
||||
self.response if self.response else None]
|
||||
|
||||
return ' - '.join([str(item) for item in s if s is not None])
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class LDAPOperationsErrorResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPProtocolErrorResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPTimeLimitExceededResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSizeLimitExceededResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAuthMethodNotSupportedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPStrongerAuthRequiredResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPReferralResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAdminLimitExceededResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnavailableCriticalExtensionResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPConfidentialityRequiredResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSASLBindInProgressResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNoSuchAttributeResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUndefinedAttributeTypeResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInappropriateMatchingResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPConstraintViolationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAttributeOrValueExistsResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidAttributeSyntaxResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNoSuchObjectResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAliasProblemResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidDNSyntaxResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAliasDereferencingProblemResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInappropriateAuthenticationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidCredentialsResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInsufficientAccessRightsResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPBusyResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnavailableResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnwillingToPerformResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLoopDetectedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNamingViolationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPObjectClassViolationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNotAllowedOnNotLeafResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNotAllowedOnRDNResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPEntryAlreadyExistsResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPObjectClassModsProhibitedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAffectMultipleDSASResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPOtherResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLCUPResourcesExhaustedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLCUPSecurityViolationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLCUPInvalidDataResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLCUPUnsupportedSchemeResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLCUPReloadRequiredResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPCanceledResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPNoSuchOperationResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPTooLateResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPCannotCancelResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAssertionFailedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAuthorizationDeniedResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPESyncRefreshRequiredResult(LDAPOperationResult):
|
||||
pass
|
||||
|
||||
|
||||
exception_table = {RESULT_OPERATIONS_ERROR: LDAPOperationsErrorResult,
|
||||
RESULT_PROTOCOL_ERROR: LDAPProtocolErrorResult,
|
||||
RESULT_TIME_LIMIT_EXCEEDED: LDAPTimeLimitExceededResult,
|
||||
RESULT_SIZE_LIMIT_EXCEEDED: LDAPSizeLimitExceededResult,
|
||||
RESULT_AUTH_METHOD_NOT_SUPPORTED: LDAPAuthMethodNotSupportedResult,
|
||||
RESULT_STRONGER_AUTH_REQUIRED: LDAPStrongerAuthRequiredResult,
|
||||
RESULT_REFERRAL: LDAPReferralResult,
|
||||
RESULT_ADMIN_LIMIT_EXCEEDED: LDAPAdminLimitExceededResult,
|
||||
RESULT_UNAVAILABLE_CRITICAL_EXTENSION: LDAPUnavailableCriticalExtensionResult,
|
||||
RESULT_CONFIDENTIALITY_REQUIRED: LDAPConfidentialityRequiredResult,
|
||||
RESULT_SASL_BIND_IN_PROGRESS: LDAPSASLBindInProgressResult,
|
||||
RESULT_NO_SUCH_ATTRIBUTE: LDAPNoSuchAttributeResult,
|
||||
RESULT_UNDEFINED_ATTRIBUTE_TYPE: LDAPUndefinedAttributeTypeResult,
|
||||
RESULT_INAPPROPRIATE_MATCHING: LDAPInappropriateMatchingResult,
|
||||
RESULT_CONSTRAINT_VIOLATION: LDAPConstraintViolationResult,
|
||||
RESULT_ATTRIBUTE_OR_VALUE_EXISTS: LDAPAttributeOrValueExistsResult,
|
||||
RESULT_INVALID_ATTRIBUTE_SYNTAX: LDAPInvalidAttributeSyntaxResult,
|
||||
RESULT_NO_SUCH_OBJECT: LDAPNoSuchObjectResult,
|
||||
RESULT_ALIAS_PROBLEM: LDAPAliasProblemResult,
|
||||
RESULT_INVALID_DN_SYNTAX: LDAPInvalidDNSyntaxResult,
|
||||
RESULT_ALIAS_DEREFERENCING_PROBLEM: LDAPAliasDereferencingProblemResult,
|
||||
RESULT_INAPPROPRIATE_AUTHENTICATION: LDAPInappropriateAuthenticationResult,
|
||||
RESULT_INVALID_CREDENTIALS: LDAPInvalidCredentialsResult,
|
||||
RESULT_INSUFFICIENT_ACCESS_RIGHTS: LDAPInsufficientAccessRightsResult,
|
||||
RESULT_BUSY: LDAPBusyResult,
|
||||
RESULT_UNAVAILABLE: LDAPUnavailableResult,
|
||||
RESULT_UNWILLING_TO_PERFORM: LDAPUnwillingToPerformResult,
|
||||
RESULT_LOOP_DETECTED: LDAPLoopDetectedResult,
|
||||
RESULT_NAMING_VIOLATION: LDAPNamingViolationResult,
|
||||
RESULT_OBJECT_CLASS_VIOLATION: LDAPObjectClassViolationResult,
|
||||
RESULT_NOT_ALLOWED_ON_NON_LEAF: LDAPNotAllowedOnNotLeafResult,
|
||||
RESULT_NOT_ALLOWED_ON_RDN: LDAPNotAllowedOnRDNResult,
|
||||
RESULT_ENTRY_ALREADY_EXISTS: LDAPEntryAlreadyExistsResult,
|
||||
RESULT_OBJECT_CLASS_MODS_PROHIBITED: LDAPObjectClassModsProhibitedResult,
|
||||
RESULT_AFFECT_MULTIPLE_DSAS: LDAPAffectMultipleDSASResult,
|
||||
RESULT_OTHER: LDAPOtherResult,
|
||||
RESULT_LCUP_RESOURCES_EXHAUSTED: LDAPLCUPResourcesExhaustedResult,
|
||||
RESULT_LCUP_SECURITY_VIOLATION: LDAPLCUPSecurityViolationResult,
|
||||
RESULT_LCUP_INVALID_DATA: LDAPLCUPInvalidDataResult,
|
||||
RESULT_LCUP_UNSUPPORTED_SCHEME: LDAPLCUPUnsupportedSchemeResult,
|
||||
RESULT_LCUP_RELOAD_REQUIRED: LDAPLCUPReloadRequiredResult,
|
||||
RESULT_CANCELED: LDAPCanceledResult,
|
||||
RESULT_NO_SUCH_OPERATION: LDAPNoSuchOperationResult,
|
||||
RESULT_TOO_LATE: LDAPTooLateResult,
|
||||
RESULT_CANNOT_CANCEL: LDAPCannotCancelResult,
|
||||
RESULT_ASSERTION_FAILED: LDAPAssertionFailedResult,
|
||||
RESULT_AUTHORIZATION_DENIED: LDAPAuthorizationDeniedResult,
|
||||
RESULT_E_SYNC_REFRESH_REQUIRED: LDAPESyncRefreshRequiredResult}
|
||||
|
||||
|
||||
class LDAPExceptionError(LDAPException):
|
||||
pass
|
||||
|
||||
|
||||
# configuration exceptions
|
||||
class LDAPConfigurationError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnknownStrategyError(LDAPConfigurationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnknownAuthenticationMethodError(LDAPConfigurationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSSLConfigurationError(LDAPConfigurationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPDefinitionError(LDAPConfigurationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPPackageUnavailableError(LDAPConfigurationError, ImportError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPConfigurationParameterError(LDAPConfigurationError):
|
||||
pass
|
||||
|
||||
|
||||
# abstract layer exceptions
|
||||
class LDAPKeyError(LDAPExceptionError, KeyError, AttributeError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPObjectError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPAttributeError(LDAPExceptionError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPCursorError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
class LDAPObjectDereferenceError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
# security exceptions
|
||||
class LDAPSSLNotSupportedError(LDAPExceptionError, ImportError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidTlsSpecificationError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidHashAlgorithmError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
# connection exceptions
|
||||
class LDAPBindError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidServerError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSASLMechanismNotSupportedError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPConnectionIsReadOnlyError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPChangeError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPServerPoolError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPServerPoolExhaustedError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidPortError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPStartTLSError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPCertificateError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUserNameNotAllowedError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUserNameIsMandatoryError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPPasswordIsMandatoryError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidFilterError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidScopeError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidDereferenceAliasesError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidValueError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPControlError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPExtensionError(LDAPExceptionError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPLDIFError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSchemaError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSASLPrepError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSASLBindInProgressError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPMetricsError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPObjectClassError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPInvalidDnError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPResponseTimeoutError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPTransactionError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
# communication exceptions
|
||||
class LDAPCommunicationError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSocketOpenError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSocketCloseError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSocketReceiveError(LDAPCommunicationError, socket.error):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSocketSendError(LDAPCommunicationError, socket.error):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPSessionTerminatedByServerError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnknownResponseError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPUnknownRequestError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPReferralError(LDAPCommunicationError):
|
||||
pass
|
||||
|
||||
|
||||
# pooling exceptions
|
||||
class LDAPConnectionPoolNameIsMandatoryError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
class LDAPConnectionPoolNotStartedError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
|
||||
# restartable strategy
|
||||
class LDAPMaximumRetriesError(LDAPExceptionError):
|
||||
def __str__(self):
|
||||
s = []
|
||||
if self.args:
|
||||
if isinstance(self.args, tuple):
|
||||
if len(self.args) > 0:
|
||||
s.append('LDAPMaximumRetriesError: ' + str(self.args[0]))
|
||||
if len(self.args) > 1:
|
||||
s.append('Exception history:')
|
||||
prev_exc = ''
|
||||
for i, exc in enumerate(self.args[1]): # args[1] contains exception history
|
||||
if str(exc[1]) != prev_exc:
|
||||
s.append((str(i).rjust(5) + ' ' + str(exc[0]) + ': ' + str(exc[1]) + ' - ' + str(exc[2])))
|
||||
prev_exc = str(exc[1])
|
||||
|
||||
if len(self.args) > 2:
|
||||
s.append('Maximum number of retries reached: ' + str(self.args[2]))
|
||||
else:
|
||||
s = [LDAPExceptionError.__str__(self)]
|
||||
|
||||
return sep.join(s)
|
||||
|
||||
|
||||
# exception factories
|
||||
def communication_exception_factory(exc_to_raise, exc):
|
||||
"""
|
||||
Generates a new exception class of the requested type (subclass of LDAPCommunication) merged with the exception raised by the interpreter
|
||||
"""
|
||||
if exc_to_raise.__name__ in [cls.__name__ for cls in LDAPCommunicationError.__subclasses__()]:
|
||||
return type(exc_to_raise.__name__, (exc_to_raise, type(exc)), dict())
|
||||
else:
|
||||
raise LDAPExceptionError('unable to generate exception type ' + str(exc_to_raise))
|
||||
|
||||
|
||||
def start_tls_exception_factory(exc_to_raise, exc):
|
||||
"""
|
||||
Generates a new exception class of the requested type (subclass of LDAPCommunication) merged with the exception raised by the interpreter
|
||||
"""
|
||||
|
||||
if exc_to_raise.__name__ == 'LDAPStartTLSError':
|
||||
return type(exc_to_raise.__name__, (exc_to_raise, type(exc)), dict())
|
||||
else:
|
||||
raise LDAPExceptionError('unable to generate exception type ' + str(exc_to_raise))
|
306
thesisenv/lib/python3.6/site-packages/ldap3/core/pooling.py
Normal file
306
thesisenv/lib/python3.6/site-packages/ldap3/core/pooling.py
Normal file
@ -0,0 +1,306 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.03.14
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, MINYEAR
|
||||
from os import linesep
|
||||
from random import randint
|
||||
from time import sleep
|
||||
|
||||
from .. import FIRST, ROUND_ROBIN, RANDOM, SEQUENCE_TYPES, STRING_TYPES, get_config_parameter
|
||||
from .exceptions import LDAPUnknownStrategyError, LDAPServerPoolError, LDAPServerPoolExhaustedError
|
||||
from .server import Server
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, NETWORK
|
||||
|
||||
POOLING_STRATEGIES = [FIRST, ROUND_ROBIN, RANDOM]
|
||||
|
||||
|
||||
class ServerPoolState(object):
|
||||
def __init__(self, server_pool):
|
||||
self.servers = [] # each element is a list: [server, last_checked_time, available]
|
||||
self.strategy = server_pool.strategy
|
||||
self.server_pool = server_pool
|
||||
self.last_used_server = 0
|
||||
self.refresh()
|
||||
self.initialize_time = datetime.now()
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated ServerPoolState: <%r>', self)
|
||||
|
||||
def __str__(self):
|
||||
s = 'servers: ' + linesep
|
||||
if self.servers:
|
||||
for server in self.servers:
|
||||
s += str(server[0]) + linesep
|
||||
else:
|
||||
s += 'None' + linesep
|
||||
s += 'Pool strategy: ' + str(self.strategy) + linesep
|
||||
s += ' - Last used server: ' + ('None' if self.last_used_server == -1 else str(self.servers[self.last_used_server][0]))
|
||||
|
||||
return s
|
||||
|
||||
def refresh(self):
|
||||
self.servers = []
|
||||
for server in self.server_pool.servers:
|
||||
self.servers.append([server, datetime(MINYEAR, 1, 1), True]) # server, smallest date ever, supposed available
|
||||
self.last_used_server = randint(0, len(self.servers) - 1)
|
||||
|
||||
def get_current_server(self):
|
||||
return self.servers[self.last_used_server][0]
|
||||
|
||||
def get_server(self):
|
||||
if self.servers:
|
||||
if self.server_pool.strategy == FIRST:
|
||||
if self.server_pool.active:
|
||||
# returns the first active server
|
||||
self.last_used_server = self.find_active_server(starting=0)
|
||||
else:
|
||||
# returns always the first server - no pooling
|
||||
self.last_used_server = 0
|
||||
elif self.server_pool.strategy == ROUND_ROBIN:
|
||||
if self.server_pool.active:
|
||||
# returns the next active server in a circular range
|
||||
self.last_used_server = self.find_active_server(self.last_used_server + 1)
|
||||
else:
|
||||
# returns the next server in a circular range
|
||||
self.last_used_server = self.last_used_server + 1 if (self.last_used_server + 1) < len(self.servers) else 0
|
||||
elif self.server_pool.strategy == RANDOM:
|
||||
if self.server_pool.active:
|
||||
self.last_used_server = self.find_active_random_server()
|
||||
else:
|
||||
# returns a random server in the pool
|
||||
self.last_used_server = randint(0, len(self.servers) - 1)
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'unknown server pooling strategy <%s>', self.server_pool.strategy)
|
||||
raise LDAPUnknownStrategyError('unknown server pooling strategy')
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'server returned from Server Pool: <%s>', self.last_used_server)
|
||||
return self.servers[self.last_used_server][0]
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'no servers in Server Pool <%s>', self)
|
||||
raise LDAPServerPoolError('no servers in server pool')
|
||||
|
||||
def find_active_random_server(self):
|
||||
counter = self.server_pool.active # can be True for "forever" or the number of cycles to try
|
||||
while counter:
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'entering loop for finding active server in pool <%s>', self)
|
||||
temp_list = self.servers[:] # copy
|
||||
while temp_list:
|
||||
# pops a random server from a temp list and checks its
|
||||
# availability, if not available tries another one
|
||||
server = temp_list.pop(randint(0, len(temp_list) - 1))
|
||||
if not server[2]: # server is offline
|
||||
if (isinstance(self.server_pool.exhaust, bool) and self.server_pool.exhaust) or (datetime.now() - server[1]).seconds < self.server_pool.exhaust: # keeps server offline
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'server <%s> excluded from checking because it is offline', server[0])
|
||||
continue
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'server <%s> reinserted in pool', server[0])
|
||||
server[1] = datetime.now()
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'checking server <%s> for availability', server[0])
|
||||
if server[0].check_availability():
|
||||
# returns a random active server in the pool
|
||||
server[2] = True
|
||||
return self.servers.index(server)
|
||||
else:
|
||||
server[2] = False
|
||||
if not isinstance(self.server_pool.active, bool):
|
||||
counter -= 1
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'no random active server available in Server Pool <%s> after maximum number of tries', self)
|
||||
raise LDAPServerPoolExhaustedError('no random active server available in server pool after maximum number of tries')
|
||||
|
||||
def find_active_server(self, starting):
|
||||
conf_pool_timeout = get_config_parameter('POOLING_LOOP_TIMEOUT')
|
||||
counter = self.server_pool.active # can be True for "forever" or the number of cycles to try
|
||||
if starting >= len(self.servers):
|
||||
starting = 0
|
||||
|
||||
while counter:
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'entering loop number <%s> for finding active server in pool <%s>', counter, self)
|
||||
index = -1
|
||||
pool_size = len(self.servers)
|
||||
while index < pool_size - 1:
|
||||
index += 1
|
||||
offset = index + starting if index + starting < pool_size else index + starting - pool_size
|
||||
if not self.servers[offset][2]: # server is offline
|
||||
if (isinstance(self.server_pool.exhaust, bool) and self.server_pool.exhaust) or (datetime.now() - self.servers[offset][1]).seconds < self.server_pool.exhaust: # keeps server offline
|
||||
if log_enabled(NETWORK):
|
||||
if isinstance(self.server_pool.exhaust, bool):
|
||||
log(NETWORK, 'server <%s> excluded from checking because is offline', self.servers[offset][0])
|
||||
else:
|
||||
log(NETWORK, 'server <%s> excluded from checking because is offline for %d seconds', self.servers[offset][0], (self.server_pool.exhaust - (datetime.now() - self.servers[offset][1]).seconds))
|
||||
continue
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'server <%s> reinserted in pool', self.servers[offset][0])
|
||||
self.servers[offset][1] = datetime.now()
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'checking server <%s> for availability', self.servers[offset][0])
|
||||
if self.servers[offset][0].check_availability():
|
||||
self.servers[offset][2] = True
|
||||
return offset
|
||||
else:
|
||||
self.servers[offset][2] = False # sets server offline
|
||||
|
||||
if not isinstance(self.server_pool.active, bool):
|
||||
counter -= 1
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'waiting for %d seconds before retrying pool servers cycle', conf_pool_timeout)
|
||||
sleep(conf_pool_timeout)
|
||||
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'no active server available in Server Pool <%s> after maximum number of tries', self)
|
||||
raise LDAPServerPoolExhaustedError('no active server available in server pool after maximum number of tries')
|
||||
|
||||
def __len__(self):
|
||||
return len(self.servers)
|
||||
|
||||
|
||||
class ServerPool(object):
|
||||
def __init__(self,
|
||||
servers=None,
|
||||
pool_strategy=ROUND_ROBIN,
|
||||
active=True,
|
||||
exhaust=False):
|
||||
|
||||
if pool_strategy not in POOLING_STRATEGIES:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'unknown pooling strategy <%s>', pool_strategy)
|
||||
raise LDAPUnknownStrategyError('unknown pooling strategy')
|
||||
if exhaust and not active:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'cannot instantiate pool with exhaust and not active')
|
||||
raise LDAPServerPoolError('pools can be exhausted only when checking for active servers')
|
||||
self.servers = []
|
||||
self.pool_states = dict()
|
||||
self.active = active
|
||||
self.exhaust = exhaust
|
||||
if isinstance(servers, SEQUENCE_TYPES + (Server, )):
|
||||
self.add(servers)
|
||||
elif isinstance(servers, STRING_TYPES):
|
||||
self.add(Server(servers))
|
||||
self.strategy = pool_strategy
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated ServerPool: <%r>', self)
|
||||
|
||||
def __str__(self):
|
||||
s = 'servers: ' + linesep
|
||||
if self.servers:
|
||||
for server in self.servers:
|
||||
s += str(server) + linesep
|
||||
else:
|
||||
s += 'None' + linesep
|
||||
s += 'Pool strategy: ' + str(self.strategy)
|
||||
s += ' - ' + 'active: ' + (str(self.active) if self.active else 'False')
|
||||
s += ' - ' + 'exhaust pool: ' + (str(self.exhaust) if self.exhaust else 'False')
|
||||
return s
|
||||
|
||||
def __repr__(self):
|
||||
r = 'ServerPool(servers='
|
||||
if self.servers:
|
||||
r += '['
|
||||
for server in self.servers:
|
||||
r += server.__repr__() + ', '
|
||||
r = r[:-2] + ']'
|
||||
else:
|
||||
r += 'None'
|
||||
r += ', pool_strategy={0.strategy!r}'.format(self)
|
||||
r += ', active={0.active!r}'.format(self)
|
||||
r += ', exhaust={0.exhaust!r}'.format(self)
|
||||
r += ')'
|
||||
|
||||
return r
|
||||
|
||||
def __len__(self):
|
||||
return len(self.servers)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.servers[item]
|
||||
|
||||
def __iter__(self):
|
||||
return self.servers.__iter__()
|
||||
|
||||
def add(self, servers):
|
||||
if isinstance(servers, Server):
|
||||
if servers not in self.servers:
|
||||
self.servers.append(servers)
|
||||
elif isinstance(servers, STRING_TYPES):
|
||||
self.servers.append(Server(servers))
|
||||
elif isinstance(servers, SEQUENCE_TYPES):
|
||||
for server in servers:
|
||||
if isinstance(server, Server):
|
||||
self.servers.append(server)
|
||||
elif isinstance(server, STRING_TYPES):
|
||||
self.servers.append(Server(server))
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'element must be a server in Server Pool <%s>', self)
|
||||
raise LDAPServerPoolError('server in ServerPool must be a Server')
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'server must be a Server of a list of Servers when adding to Server Pool <%s>', self)
|
||||
raise LDAPServerPoolError('server must be a Server or a list of Server')
|
||||
|
||||
for connection in self.pool_states:
|
||||
# notifies connections using this pool to refresh
|
||||
self.pool_states[connection].refresh()
|
||||
|
||||
def remove(self, server):
|
||||
if server in self.servers:
|
||||
self.servers.remove(server)
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'server %s to be removed not in Server Pool <%s>', server, self)
|
||||
raise LDAPServerPoolError('server not in server pool')
|
||||
|
||||
for connection in self.pool_states:
|
||||
# notifies connections using this pool to refresh
|
||||
self.pool_states[connection].refresh()
|
||||
|
||||
def initialize(self, connection):
|
||||
pool_state = ServerPoolState(self)
|
||||
# registers pool_state in ServerPool object
|
||||
self.pool_states[connection] = pool_state
|
||||
|
||||
def get_server(self, connection):
|
||||
if connection in self.pool_states:
|
||||
return self.pool_states[connection].get_server()
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'connection <%s> not in Server Pool State <%s>', connection, self)
|
||||
raise LDAPServerPoolError('connection not in ServerPoolState')
|
||||
|
||||
def get_current_server(self, connection):
|
||||
if connection in self.pool_states:
|
||||
return self.pool_states[connection].get_current_server()
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'connection <%s> not in Server Pool State <%s>', connection, self)
|
||||
raise LDAPServerPoolError('connection not in ServerPoolState')
|
137
thesisenv/lib/python3.6/site-packages/ldap3/core/results.py
Normal file
137
thesisenv/lib/python3.6/site-packages/ldap3/core/results.py
Normal file
@ -0,0 +1,137 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.08.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# result codes
|
||||
RESULT_SUCCESS = 0
|
||||
RESULT_OPERATIONS_ERROR = 1
|
||||
RESULT_PROTOCOL_ERROR = 2
|
||||
RESULT_TIME_LIMIT_EXCEEDED = 3
|
||||
RESULT_SIZE_LIMIT_EXCEEDED = 4
|
||||
RESULT_COMPARE_FALSE = 5
|
||||
RESULT_COMPARE_TRUE = 6
|
||||
RESULT_AUTH_METHOD_NOT_SUPPORTED = 7
|
||||
RESULT_STRONGER_AUTH_REQUIRED = 8
|
||||
RESULT_RESERVED = 9
|
||||
RESULT_REFERRAL = 10
|
||||
RESULT_ADMIN_LIMIT_EXCEEDED = 11
|
||||
RESULT_UNAVAILABLE_CRITICAL_EXTENSION = 12
|
||||
RESULT_CONFIDENTIALITY_REQUIRED = 13
|
||||
RESULT_SASL_BIND_IN_PROGRESS = 14
|
||||
RESULT_NO_SUCH_ATTRIBUTE = 16
|
||||
RESULT_UNDEFINED_ATTRIBUTE_TYPE = 17
|
||||
RESULT_INAPPROPRIATE_MATCHING = 18
|
||||
RESULT_CONSTRAINT_VIOLATION = 19
|
||||
RESULT_ATTRIBUTE_OR_VALUE_EXISTS = 20
|
||||
RESULT_INVALID_ATTRIBUTE_SYNTAX = 21
|
||||
RESULT_NO_SUCH_OBJECT = 32
|
||||
RESULT_ALIAS_PROBLEM = 33
|
||||
RESULT_INVALID_DN_SYNTAX = 34
|
||||
RESULT_ALIAS_DEREFERENCING_PROBLEM = 36
|
||||
RESULT_INAPPROPRIATE_AUTHENTICATION = 48
|
||||
RESULT_INVALID_CREDENTIALS = 49
|
||||
RESULT_INSUFFICIENT_ACCESS_RIGHTS = 50
|
||||
RESULT_BUSY = 51
|
||||
RESULT_UNAVAILABLE = 52
|
||||
RESULT_UNWILLING_TO_PERFORM = 53
|
||||
RESULT_LOOP_DETECTED = 54
|
||||
RESULT_NAMING_VIOLATION = 64
|
||||
RESULT_OBJECT_CLASS_VIOLATION = 65
|
||||
RESULT_NOT_ALLOWED_ON_NON_LEAF = 66
|
||||
RESULT_NOT_ALLOWED_ON_RDN = 67
|
||||
RESULT_ENTRY_ALREADY_EXISTS = 68
|
||||
RESULT_OBJECT_CLASS_MODS_PROHIBITED = 69
|
||||
RESULT_AFFECT_MULTIPLE_DSAS = 71
|
||||
RESULT_OTHER = 80
|
||||
RESULT_LCUP_RESOURCES_EXHAUSTED = 113
|
||||
RESULT_LCUP_SECURITY_VIOLATION = 114
|
||||
RESULT_LCUP_INVALID_DATA = 115
|
||||
RESULT_LCUP_UNSUPPORTED_SCHEME = 116
|
||||
RESULT_LCUP_RELOAD_REQUIRED = 117
|
||||
RESULT_CANCELED = 118
|
||||
RESULT_NO_SUCH_OPERATION = 119
|
||||
RESULT_TOO_LATE = 120
|
||||
RESULT_CANNOT_CANCEL = 121
|
||||
RESULT_ASSERTION_FAILED = 122
|
||||
RESULT_AUTHORIZATION_DENIED = 123
|
||||
RESULT_E_SYNC_REFRESH_REQUIRED = 4096
|
||||
|
||||
RESULT_CODES = {
|
||||
RESULT_SUCCESS: 'success',
|
||||
RESULT_OPERATIONS_ERROR: 'operationsError',
|
||||
RESULT_PROTOCOL_ERROR: 'protocolError',
|
||||
RESULT_TIME_LIMIT_EXCEEDED: 'timeLimitExceeded',
|
||||
RESULT_SIZE_LIMIT_EXCEEDED: 'sizeLimitExceeded',
|
||||
RESULT_COMPARE_FALSE: 'compareFalse',
|
||||
RESULT_COMPARE_TRUE: 'compareTrue',
|
||||
RESULT_AUTH_METHOD_NOT_SUPPORTED: 'authMethodNotSupported',
|
||||
RESULT_RESERVED: 'reserved',
|
||||
RESULT_STRONGER_AUTH_REQUIRED: 'strongerAuthRequired',
|
||||
RESULT_REFERRAL: 'referral',
|
||||
RESULT_ADMIN_LIMIT_EXCEEDED: 'adminLimitExceeded',
|
||||
RESULT_UNAVAILABLE_CRITICAL_EXTENSION: 'unavailableCriticalExtension',
|
||||
RESULT_CONFIDENTIALITY_REQUIRED: 'confidentialityRequired',
|
||||
RESULT_SASL_BIND_IN_PROGRESS: 'saslBindInProgress',
|
||||
RESULT_NO_SUCH_ATTRIBUTE: 'noSuchAttribute',
|
||||
RESULT_UNDEFINED_ATTRIBUTE_TYPE: 'undefinedAttributeType',
|
||||
RESULT_INAPPROPRIATE_MATCHING: 'inappropriateMatching',
|
||||
RESULT_CONSTRAINT_VIOLATION: 'constraintViolation',
|
||||
RESULT_ATTRIBUTE_OR_VALUE_EXISTS: 'attributeOrValueExists',
|
||||
RESULT_INVALID_ATTRIBUTE_SYNTAX: 'invalidAttributeSyntax',
|
||||
RESULT_NO_SUCH_OBJECT: 'noSuchObject',
|
||||
RESULT_ALIAS_PROBLEM: 'aliasProblem',
|
||||
RESULT_INVALID_DN_SYNTAX: 'invalidDNSyntax',
|
||||
RESULT_ALIAS_DEREFERENCING_PROBLEM: 'aliasDereferencingProblem',
|
||||
RESULT_INAPPROPRIATE_AUTHENTICATION: 'inappropriateAuthentication',
|
||||
RESULT_INVALID_CREDENTIALS: 'invalidCredentials',
|
||||
RESULT_INSUFFICIENT_ACCESS_RIGHTS: 'insufficientAccessRights',
|
||||
RESULT_BUSY: 'busy',
|
||||
RESULT_UNAVAILABLE: 'unavailable',
|
||||
RESULT_UNWILLING_TO_PERFORM: 'unwillingToPerform',
|
||||
RESULT_LOOP_DETECTED: 'loopDetected',
|
||||
RESULT_NAMING_VIOLATION: 'namingViolation',
|
||||
RESULT_OBJECT_CLASS_VIOLATION: 'objectClassViolation',
|
||||
RESULT_NOT_ALLOWED_ON_NON_LEAF: 'notAllowedOnNonLeaf',
|
||||
RESULT_NOT_ALLOWED_ON_RDN: 'notAllowedOnRDN',
|
||||
RESULT_ENTRY_ALREADY_EXISTS: 'entryAlreadyExists',
|
||||
RESULT_OBJECT_CLASS_MODS_PROHIBITED: 'objectClassModsProhibited',
|
||||
RESULT_AFFECT_MULTIPLE_DSAS: 'affectMultipleDSAs',
|
||||
RESULT_OTHER: 'other',
|
||||
RESULT_LCUP_RESOURCES_EXHAUSTED: 'lcupResourcesExhausted',
|
||||
RESULT_LCUP_SECURITY_VIOLATION: 'lcupSecurityViolation',
|
||||
RESULT_LCUP_INVALID_DATA: 'lcupInvalidData',
|
||||
RESULT_LCUP_UNSUPPORTED_SCHEME: 'lcupUnsupportedScheme',
|
||||
RESULT_LCUP_RELOAD_REQUIRED: 'lcupReloadRequired',
|
||||
RESULT_CANCELED: 'canceled',
|
||||
RESULT_NO_SUCH_OPERATION: 'noSuchOperation',
|
||||
RESULT_TOO_LATE: 'tooLate',
|
||||
RESULT_CANNOT_CANCEL: 'cannotCancel',
|
||||
RESULT_ASSERTION_FAILED: 'assertionFailed',
|
||||
RESULT_AUTHORIZATION_DENIED: 'authorizationDenied',
|
||||
RESULT_E_SYNC_REFRESH_REQUIRED: 'e-syncRefreshRequired'
|
||||
}
|
||||
|
||||
# do not raise exception for (in raise_exceptions connection mode)
|
||||
DO_NOT_RAISE_EXCEPTIONS = [RESULT_SUCCESS, RESULT_COMPARE_FALSE, RESULT_COMPARE_TRUE, RESULT_REFERRAL, RESULT_SASL_BIND_IN_PROGRESS]
|
572
thesisenv/lib/python3.6/site-packages/ldap3/core/server.py
Normal file
572
thesisenv/lib/python3.6/site-packages/ldap3/core/server.py
Normal file
@ -0,0 +1,572 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.05.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import socket
|
||||
from threading import Lock
|
||||
from datetime import datetime, MINYEAR
|
||||
|
||||
from .. import DSA, SCHEMA, ALL, BASE, get_config_parameter, OFFLINE_EDIR_8_8_8, OFFLINE_AD_2012_R2, OFFLINE_SLAPD_2_4, OFFLINE_DS389_1_3_3, SEQUENCE_TYPES, IP_SYSTEM_DEFAULT, IP_V4_ONLY, IP_V6_ONLY, IP_V4_PREFERRED, IP_V6_PREFERRED, STRING_TYPES
|
||||
from .exceptions import LDAPInvalidServerError, LDAPDefinitionError, LDAPInvalidPortError, LDAPInvalidTlsSpecificationError, LDAPSocketOpenError
|
||||
from ..protocol.formatters.standard import format_attribute_values
|
||||
from ..protocol.rfc4511 import LDAP_MAX_INT
|
||||
from ..protocol.rfc4512 import SchemaInfo, DsaInfo
|
||||
from .tls import Tls
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL
|
||||
from ..utils.conv import to_unicode
|
||||
|
||||
try:
|
||||
from urllib.parse import unquote # Python 3
|
||||
except ImportError:
|
||||
from urllib import unquote # Python 2
|
||||
|
||||
try: # try to discover if unix sockets are available for LDAP over IPC (ldapi:// scheme)
|
||||
# noinspection PyUnresolvedReferences
|
||||
from socket import AF_UNIX
|
||||
unix_socket_available = True
|
||||
except ImportError:
|
||||
unix_socket_available = False
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""
|
||||
LDAP Server definition class
|
||||
|
||||
Allowed_referral_hosts can be None (default), or a list of tuples of
|
||||
allowed servers ip address or names to contact while redirecting
|
||||
search to referrals.
|
||||
|
||||
The second element of the tuple is a boolean to indicate if
|
||||
authentication to that server is allowed; if False only anonymous
|
||||
bind will be used.
|
||||
|
||||
Per RFC 4516. Use [('*', False)] to allow any host with anonymous
|
||||
bind, use [('*', True)] to allow any host with same authentication of
|
||||
Server.
|
||||
"""
|
||||
|
||||
_message_counter = 0
|
||||
_message_id_lock = Lock() # global lock for message_id shared by all Server objects
|
||||
|
||||
|
||||
def __init__(self,
|
||||
host,
|
||||
port=None,
|
||||
use_ssl=False,
|
||||
allowed_referral_hosts=None,
|
||||
get_info=SCHEMA,
|
||||
tls=None,
|
||||
formatter=None,
|
||||
connect_timeout=None,
|
||||
mode=IP_V6_PREFERRED,
|
||||
validator=None):
|
||||
|
||||
self.ipc = False
|
||||
url_given = False
|
||||
host = host.strip()
|
||||
if host.lower().startswith('ldap://'):
|
||||
self.host = host[7:]
|
||||
use_ssl = False
|
||||
url_given = True
|
||||
elif host.lower().startswith('ldaps://'):
|
||||
self.host = host[8:]
|
||||
use_ssl = True
|
||||
url_given = True
|
||||
elif host.lower().startswith('ldapi://') and unix_socket_available:
|
||||
self.ipc = True
|
||||
use_ssl = False
|
||||
url_given = True
|
||||
elif host.lower().startswith('ldapi://') and not unix_socket_available:
|
||||
raise LDAPSocketOpenError('LDAP over IPC not available - UNIX sockets non present')
|
||||
else:
|
||||
self.host = host
|
||||
|
||||
if self.ipc:
|
||||
if str is bytes: # Python 2
|
||||
self.host = unquote(host[7:]).decode('utf-8')
|
||||
else: # Python 3
|
||||
self.host = unquote(host[7:]) # encoding defaults to utf-8 in python3
|
||||
self.port = None
|
||||
elif ':' in self.host and self.host.count(':') == 1:
|
||||
hostname, _, hostport = self.host.partition(':')
|
||||
try:
|
||||
port = int(hostport) or port
|
||||
except ValueError:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'port <%s> must be an integer', port)
|
||||
raise LDAPInvalidPortError('port must be an integer')
|
||||
self.host = hostname
|
||||
elif url_given and self.host.startswith('['):
|
||||
hostname, sep, hostport = self.host[1:].partition(']')
|
||||
if sep != ']' or not self._is_ipv6(hostname):
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid IPv6 server address for <%s>', self.host)
|
||||
raise LDAPInvalidServerError()
|
||||
if len(hostport):
|
||||
if not hostport.startswith(':'):
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid URL in server name for <%s>', self.host)
|
||||
raise LDAPInvalidServerError('invalid URL in server name')
|
||||
if not hostport[1:].isdecimal():
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'port must be an integer for <%s>', self.host)
|
||||
raise LDAPInvalidPortError('port must be an integer')
|
||||
port = int(hostport[1:])
|
||||
self.host = hostname
|
||||
elif not url_given and self._is_ipv6(self.host):
|
||||
pass
|
||||
elif self.host.count(':') > 1:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid server address for <%s>', self.host)
|
||||
raise LDAPInvalidServerError()
|
||||
|
||||
if not self.ipc:
|
||||
self.host.rstrip('/')
|
||||
if not use_ssl and not port:
|
||||
port = 389
|
||||
elif use_ssl and not port:
|
||||
port = 636
|
||||
|
||||
if isinstance(port, int):
|
||||
if port in range(0, 65535):
|
||||
self.port = port
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'port <%s> must be in range from 0 to 65535', port)
|
||||
raise LDAPInvalidPortError('port must in range from 0 to 65535')
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'port <%s> must be an integer', port)
|
||||
raise LDAPInvalidPortError('port must be an integer')
|
||||
|
||||
if allowed_referral_hosts is None: # defaults to any server with authentication
|
||||
allowed_referral_hosts = [('*', True)]
|
||||
|
||||
if isinstance(allowed_referral_hosts, SEQUENCE_TYPES):
|
||||
self.allowed_referral_hosts = []
|
||||
for referral_host in allowed_referral_hosts:
|
||||
if isinstance(referral_host, tuple):
|
||||
if isinstance(referral_host[1], bool):
|
||||
self.allowed_referral_hosts.append(referral_host)
|
||||
elif isinstance(allowed_referral_hosts, tuple):
|
||||
if isinstance(allowed_referral_hosts[1], bool):
|
||||
self.allowed_referral_hosts = [allowed_referral_hosts]
|
||||
else:
|
||||
self.allowed_referral_hosts = []
|
||||
|
||||
self.ssl = True if use_ssl else False
|
||||
if tls and not isinstance(tls, Tls):
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid tls specification: <%s>', tls)
|
||||
raise LDAPInvalidTlsSpecificationError('invalid Tls object')
|
||||
|
||||
self.tls = Tls() if self.ssl and not tls else tls
|
||||
|
||||
if not self.ipc:
|
||||
if self._is_ipv6(self.host):
|
||||
self.name = ('ldaps' if self.ssl else 'ldap') + '://[' + self.host + ']:' + str(self.port)
|
||||
else:
|
||||
self.name = ('ldaps' if self.ssl else 'ldap') + '://' + self.host + ':' + str(self.port)
|
||||
else:
|
||||
self.name = host
|
||||
|
||||
self.get_info = get_info
|
||||
self._dsa_info = None
|
||||
self._schema_info = None
|
||||
self.dit_lock = Lock()
|
||||
self.custom_formatter = formatter
|
||||
self.custom_validator = validator
|
||||
self._address_info = [] # property self.address_info resolved at open time (or when check_availability is called)
|
||||
self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date ever
|
||||
self.current_address = None
|
||||
self.connect_timeout = connect_timeout
|
||||
self.mode = mode
|
||||
|
||||
self.get_info_from_server(None) # load offline schema if needed
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Server: <%r>', self)
|
||||
|
||||
@staticmethod
|
||||
def _is_ipv6(host):
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, host)
|
||||
except (socket.error, AttributeError, ValueError):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
if self.host:
|
||||
s = self.name + (' - ssl' if self.ssl else ' - cleartext') + (' - unix socket' if self.ipc else '')
|
||||
else:
|
||||
s = object.__str__(self)
|
||||
return s
|
||||
|
||||
def __repr__(self):
|
||||
r = 'Server(host={0.host!r}, port={0.port!r}, use_ssl={0.ssl!r}'.format(self)
|
||||
r += '' if not self.allowed_referral_hosts else ', allowed_referral_hosts={0.allowed_referral_hosts!r}'.format(self)
|
||||
r += '' if self.tls is None else ', tls={0.tls!r}'.format(self)
|
||||
r += '' if not self.get_info else ', get_info={0.get_info!r}'.format(self)
|
||||
r += '' if not self.connect_timeout else ', connect_timeout={0.connect_timeout!r}'.format(self)
|
||||
r += '' if not self.mode else ', mode={0.mode!r}'.format(self)
|
||||
r += ')'
|
||||
|
||||
return r
|
||||
|
||||
@property
|
||||
def address_info(self):
|
||||
conf_refresh_interval = get_config_parameter('ADDRESS_INFO_REFRESH_TIME')
|
||||
if not self._address_info or (datetime.now() - self._address_info_resolved_time).seconds > conf_refresh_interval:
|
||||
# converts addresses tuple to list and adds a 6th parameter for availability (None = not checked, True = available, False=not available) and a 7th parameter for the checking time
|
||||
addresses = None
|
||||
try:
|
||||
if self.ipc:
|
||||
addresses = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, self.host, None)]
|
||||
else:
|
||||
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
|
||||
except (socket.gaierror, AttributeError):
|
||||
pass
|
||||
|
||||
if not addresses: # if addresses not found or raised an exception (for example for bad flags) tries again without flags
|
||||
try:
|
||||
addresses = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
||||
except socket.gaierror:
|
||||
pass
|
||||
|
||||
if addresses:
|
||||
self._address_info = [list(address) + [None, None] for address in addresses]
|
||||
self._address_info_resolved_time = datetime.now()
|
||||
else:
|
||||
self._address_info = []
|
||||
self._address_info_resolved_time = datetime(MINYEAR, 1, 1) # smallest date
|
||||
|
||||
if log_enabled(BASIC):
|
||||
for address in self._address_info:
|
||||
log(BASIC, 'address for <%s> resolved as <%r>', self, address[:-2])
|
||||
return self._address_info
|
||||
|
||||
def update_availability(self, address, available):
|
||||
cont = 0
|
||||
while cont < len(self._address_info):
|
||||
if self.address_info[cont] == address:
|
||||
self._address_info[cont][5] = True if available else False
|
||||
self._address_info[cont][6] = datetime.now()
|
||||
break
|
||||
cont += 1
|
||||
|
||||
def reset_availability(self):
|
||||
for address in self._address_info:
|
||||
address[5] = None
|
||||
address[6] = None
|
||||
|
||||
def check_availability(self):
|
||||
"""
|
||||
Tries to open, connect and close a socket to specified address
|
||||
and port to check availability. Timeout in seconds is specified in CHECK_AVAILABITY_TIMEOUT if not specified in
|
||||
the Server object
|
||||
"""
|
||||
conf_availability_timeout = get_config_parameter('CHECK_AVAILABILITY_TIMEOUT')
|
||||
available = False
|
||||
self.reset_availability()
|
||||
for address in self.candidate_addresses():
|
||||
available = True
|
||||
try:
|
||||
temp_socket = socket.socket(*address[:3])
|
||||
if self.connect_timeout:
|
||||
temp_socket.settimeout(self.connect_timeout)
|
||||
else:
|
||||
temp_socket.settimeout(conf_availability_timeout) # set timeout for checking availability to default
|
||||
try:
|
||||
temp_socket.connect(address[4])
|
||||
except socket.error:
|
||||
available = False
|
||||
finally:
|
||||
try:
|
||||
temp_socket.shutdown(socket.SHUT_RDWR)
|
||||
except socket.error:
|
||||
available = False
|
||||
finally:
|
||||
temp_socket.close()
|
||||
except socket.gaierror:
|
||||
available = False
|
||||
|
||||
if available:
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'server <%s> available at <%r>', self, address)
|
||||
self.update_availability(address, True)
|
||||
break # if an available address is found exits immediately
|
||||
else:
|
||||
self.update_availability(address, False)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'server <%s> not available at <%r>', self, address)
|
||||
|
||||
return available
|
||||
|
||||
@staticmethod
|
||||
def next_message_id():
|
||||
"""
|
||||
LDAP messageId is unique for all connections to same server
|
||||
"""
|
||||
with Server._message_id_lock:
|
||||
Server._message_counter += 1
|
||||
if Server._message_counter >= LDAP_MAX_INT:
|
||||
Server._message_counter = 1
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'new message id <%d> generated', Server._message_counter)
|
||||
|
||||
return Server._message_counter
|
||||
|
||||
def _get_dsa_info(self, connection):
|
||||
"""
|
||||
Retrieve DSE operational attribute as per RFC4512 (5.1).
|
||||
"""
|
||||
if connection.strategy.no_real_dsa: # do not try for mock strategies
|
||||
return
|
||||
|
||||
if not connection.strategy.pooled: # in pooled strategies get_dsa_info is performed by the worker threads
|
||||
result = connection.search(search_base='',
|
||||
search_filter='(objectClass=*)',
|
||||
search_scope=BASE,
|
||||
attributes=['altServer', # requests specific dsa info attributes
|
||||
'namingContexts',
|
||||
'supportedControl',
|
||||
'supportedExtension',
|
||||
'supportedFeatures',
|
||||
'supportedCapabilities',
|
||||
'supportedLdapVersion',
|
||||
'supportedSASLMechanisms',
|
||||
'vendorName',
|
||||
'vendorVersion',
|
||||
'subschemaSubentry',
|
||||
'*',
|
||||
'+'], # requests all remaining attributes (other),
|
||||
get_operational_attributes=True)
|
||||
|
||||
with self.dit_lock:
|
||||
if isinstance(result, bool): # sync request
|
||||
self._dsa_info = DsaInfo(connection.response[0]['attributes'], connection.response[0]['raw_attributes']) if result else self._dsa_info
|
||||
elif result: # asynchronous request, must check if attributes in response
|
||||
results, _ = connection.get_response(result)
|
||||
if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
|
||||
self._dsa_info = DsaInfo(results[0]['attributes'], results[0]['raw_attributes'])
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'DSA info read for <%s> via <%s>', self, connection)
|
||||
|
||||
def _get_schema_info(self, connection, entry=''):
|
||||
"""
|
||||
Retrieve schema from subschemaSubentry DSE attribute, per RFC
|
||||
4512 (4.4 and 5.1); entry = '' means DSE.
|
||||
"""
|
||||
if connection.strategy.no_real_dsa: # do not try for mock strategies
|
||||
return
|
||||
|
||||
schema_entry = None
|
||||
if self._dsa_info and entry == '': # subschemaSubentry already present in dsaInfo
|
||||
if isinstance(self._dsa_info.schema_entry, SEQUENCE_TYPES):
|
||||
schema_entry = self._dsa_info.schema_entry[0] if self._dsa_info.schema_entry else None
|
||||
else:
|
||||
schema_entry = self._dsa_info.schema_entry if self._dsa_info.schema_entry else None
|
||||
else:
|
||||
result = connection.search(entry, '(objectClass=*)', BASE, attributes=['subschemaSubentry'], get_operational_attributes=True)
|
||||
if isinstance(result, bool): # sync request
|
||||
if result and 'subschemaSubentry' in connection.response[0]['raw_attributes']:
|
||||
if len(connection.response[0]['raw_attributes']['subschemaSubentry']) > 0:
|
||||
schema_entry = connection.response[0]['raw_attributes']['subschemaSubentry'][0]
|
||||
else: # asynchronous request, must check if subschemaSubentry in attributes
|
||||
results, _ = connection.get_response(result)
|
||||
if len(results) == 1 and 'raw_attributes' in results[0] and 'subschemaSubentry' in results[0]['attributes']:
|
||||
if len(results[0]['raw_attributes']['subschemaSubentry']) > 0:
|
||||
schema_entry = results[0]['raw_attributes']['subschemaSubentry'][0]
|
||||
|
||||
if schema_entry and not connection.strategy.pooled: # in pooled strategies get_schema_info is performed by the worker threads
|
||||
if isinstance(schema_entry, bytes) and str is not bytes: # Python 3
|
||||
schema_entry = to_unicode(schema_entry, from_server=True)
|
||||
result = connection.search(schema_entry,
|
||||
search_filter='(objectClass=subschema)',
|
||||
search_scope=BASE,
|
||||
attributes=['objectClasses', # requests specific subschema attributes
|
||||
'attributeTypes',
|
||||
'ldapSyntaxes',
|
||||
'matchingRules',
|
||||
'matchingRuleUse',
|
||||
'dITContentRules',
|
||||
'dITStructureRules',
|
||||
'nameForms',
|
||||
'createTimestamp',
|
||||
'modifyTimestamp',
|
||||
'*'], # requests all remaining attributes (other)
|
||||
get_operational_attributes=True
|
||||
)
|
||||
with self.dit_lock:
|
||||
self._schema_info = None
|
||||
if result:
|
||||
if isinstance(result, bool): # sync request
|
||||
self._schema_info = SchemaInfo(schema_entry, connection.response[0]['attributes'], connection.response[0]['raw_attributes']) if result else None
|
||||
else: # asynchronous request, must check if attributes in response
|
||||
results, result = connection.get_response(result)
|
||||
if len(results) == 1 and 'attributes' in results[0] and 'raw_attributes' in results[0]:
|
||||
self._schema_info = SchemaInfo(schema_entry, results[0]['attributes'], results[0]['raw_attributes'])
|
||||
if self._schema_info and not self._schema_info.is_valid(): # flaky servers can return an empty schema, checks if it is so and set schema to None
|
||||
self._schema_info = None
|
||||
if self._schema_info: # if schema is valid tries to apply formatter to the "other" dict with raw values for schema and info
|
||||
for attribute in self._schema_info.other:
|
||||
self._schema_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._schema_info.raw[attribute], self.custom_formatter)
|
||||
if self._dsa_info: # try to apply formatter to the "other" dict with dsa info raw values
|
||||
for attribute in self._dsa_info.other:
|
||||
self._dsa_info.other[attribute] = format_attribute_values(self._schema_info, attribute, self._dsa_info.raw[attribute], self.custom_formatter)
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'schema read for <%s> via <%s>', self, connection)
|
||||
|
||||
def get_info_from_server(self, connection):
|
||||
"""
|
||||
reads info from DSE and from subschema
|
||||
"""
|
||||
if connection and not connection.closed:
|
||||
if self.get_info in [DSA, ALL]:
|
||||
self._get_dsa_info(connection)
|
||||
if self.get_info in [SCHEMA, ALL]:
|
||||
self._get_schema_info(connection)
|
||||
elif self.get_info == OFFLINE_EDIR_8_8_8:
|
||||
from ..protocol.schemas.edir888 import edir_8_8_8_schema, edir_8_8_8_dsa_info
|
||||
self.attach_schema_info(SchemaInfo.from_json(edir_8_8_8_schema))
|
||||
self.attach_dsa_info(DsaInfo.from_json(edir_8_8_8_dsa_info))
|
||||
elif self.get_info == OFFLINE_AD_2012_R2:
|
||||
from ..protocol.schemas.ad2012R2 import ad_2012_r2_schema, ad_2012_r2_dsa_info
|
||||
self.attach_schema_info(SchemaInfo.from_json(ad_2012_r2_schema))
|
||||
self.attach_dsa_info(DsaInfo.from_json(ad_2012_r2_dsa_info))
|
||||
elif self.get_info == OFFLINE_SLAPD_2_4:
|
||||
from ..protocol.schemas.slapd24 import slapd_2_4_schema, slapd_2_4_dsa_info
|
||||
self.attach_schema_info(SchemaInfo.from_json(slapd_2_4_schema))
|
||||
self.attach_dsa_info(DsaInfo.from_json(slapd_2_4_dsa_info))
|
||||
elif self.get_info == OFFLINE_DS389_1_3_3:
|
||||
from ..protocol.schemas.ds389 import ds389_1_3_3_schema, ds389_1_3_3_dsa_info
|
||||
self.attach_schema_info(SchemaInfo.from_json(ds389_1_3_3_schema))
|
||||
self.attach_dsa_info(DsaInfo.from_json(ds389_1_3_3_dsa_info))
|
||||
|
||||
def attach_dsa_info(self, dsa_info=None):
|
||||
if isinstance(dsa_info, DsaInfo):
|
||||
self._dsa_info = dsa_info
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'attached DSA info to Server <%s>', self)
|
||||
|
||||
def attach_schema_info(self, dsa_schema=None):
|
||||
if isinstance(dsa_schema, SchemaInfo):
|
||||
self._schema_info = dsa_schema
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'attached schema info to Server <%s>', self)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self._dsa_info
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return self._schema_info
|
||||
|
||||
@staticmethod
|
||||
def from_definition(host, dsa_info, dsa_schema, port=None, use_ssl=False, formatter=None, validator=None):
|
||||
"""
|
||||
Define a dummy server with preloaded schema and info
|
||||
:param host: host name
|
||||
:param dsa_info: DsaInfo preloaded object or a json formatted string or a file name
|
||||
:param dsa_schema: SchemaInfo preloaded object or a json formatted string or a file name
|
||||
:param port: dummy port
|
||||
:param use_ssl: use_ssl
|
||||
:param formatter: custom formatter
|
||||
:return: Server object
|
||||
"""
|
||||
if isinstance(host, SEQUENCE_TYPES):
|
||||
dummy = Server(host=host[0], port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL) # for ServerPool object
|
||||
else:
|
||||
dummy = Server(host=host, port=port, use_ssl=use_ssl, formatter=formatter, validator=validator, get_info=ALL)
|
||||
if isinstance(dsa_info, DsaInfo):
|
||||
dummy._dsa_info = dsa_info
|
||||
elif isinstance(dsa_info, STRING_TYPES):
|
||||
try:
|
||||
dummy._dsa_info = DsaInfo.from_json(dsa_info) # tries to use dsa_info as a json configuration string
|
||||
except Exception:
|
||||
dummy._dsa_info = DsaInfo.from_file(dsa_info) # tries to use dsa_info as a file name
|
||||
|
||||
if not dummy.info:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid DSA info for %s', host)
|
||||
raise LDAPDefinitionError('invalid dsa info')
|
||||
|
||||
if isinstance(dsa_schema, SchemaInfo):
|
||||
dummy._schema_info = dsa_schema
|
||||
elif isinstance(dsa_schema, STRING_TYPES):
|
||||
try:
|
||||
dummy._schema_info = SchemaInfo.from_json(dsa_schema)
|
||||
except Exception:
|
||||
dummy._schema_info = SchemaInfo.from_file(dsa_schema)
|
||||
|
||||
if not dummy.schema:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid schema info for %s', host)
|
||||
raise LDAPDefinitionError('invalid schema info')
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'created server <%s> from definition', dummy)
|
||||
|
||||
return dummy
|
||||
|
||||
def candidate_addresses(self):
|
||||
conf_reset_availability_timeout = get_config_parameter('RESET_AVAILABILITY_TIMEOUT')
|
||||
if self.ipc:
|
||||
candidates = self.address_info
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'candidate address for <%s>: <%s> with mode UNIX_SOCKET', self, self.name)
|
||||
else:
|
||||
# checks reset availability timeout
|
||||
for address in self.address_info:
|
||||
if address[6] and ((datetime.now() - address[6]).seconds > conf_reset_availability_timeout):
|
||||
address[5] = None
|
||||
address[6] = None
|
||||
|
||||
# selects server address based on server mode and availability (in address[5])
|
||||
addresses = self.address_info[:] # copy to avoid refreshing while searching candidates
|
||||
candidates = []
|
||||
if addresses:
|
||||
if self.mode == IP_SYSTEM_DEFAULT:
|
||||
candidates.append(addresses[0])
|
||||
elif self.mode == IP_V4_ONLY:
|
||||
candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
|
||||
elif self.mode == IP_V6_ONLY:
|
||||
candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
|
||||
elif self.mode == IP_V4_PREFERRED:
|
||||
candidates = [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
|
||||
candidates += [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
|
||||
elif self.mode == IP_V6_PREFERRED:
|
||||
candidates = [address for address in addresses if address[0] == socket.AF_INET6 and (address[5] or address[5] is None)]
|
||||
candidates += [address for address in addresses if address[0] == socket.AF_INET and (address[5] or address[5] is None)]
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid server mode for <%s>', self)
|
||||
raise LDAPInvalidServerError('invalid server mode')
|
||||
|
||||
if log_enabled(BASIC):
|
||||
for candidate in candidates:
|
||||
log(BASIC, 'obtained candidate address for <%s>: <%r> with mode %s', self, candidate[:-2], self.mode)
|
||||
return candidates
|
56
thesisenv/lib/python3.6/site-packages/ldap3/core/timezone.py
Normal file
56
thesisenv/lib/python3.6/site-packages/ldap3/core/timezone.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.01.07
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import timedelta, tzinfo
|
||||
|
||||
|
||||
# from python standard library docs
|
||||
class OffsetTzInfo(tzinfo):
|
||||
"""Fixed offset in minutes east from UTC"""
|
||||
|
||||
def __init__(self, offset, name):
|
||||
self.offset = offset
|
||||
self.name = name
|
||||
self._offset = timedelta(minutes=offset)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return 'OffsetTzInfo(offset={0.offset!r}, name={0.name!r})'.format(self)
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return self._offset
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.name
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def dst(self, dt):
|
||||
return timedelta(0)
|
||||
|
||||
def __getinitargs__(self): # for pickling/unpickling
|
||||
return self.offset, self.name
|
326
thesisenv/lib/python3.6/site-packages/ldap3/core/tls.py
Normal file
326
thesisenv/lib/python3.6/site-packages/ldap3/core/tls.py
Normal file
@ -0,0 +1,326 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2013.08.05
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .exceptions import LDAPSSLNotSupportedError, LDAPSSLConfigurationError, LDAPStartTLSError, LDAPCertificateError, start_tls_exception_factory
|
||||
from .. import SEQUENCE_TYPES
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC, NETWORK
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import ssl
|
||||
except ImportError:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'SSL not supported in this Python interpreter')
|
||||
raise LDAPSSLNotSupportedError('SSL not supported in this Python interpreter')
|
||||
|
||||
try:
|
||||
from ssl import match_hostname, CertificateError # backport for python2 missing ssl functionalities
|
||||
except ImportError:
|
||||
from ..utils.tls_backport import CertificateError
|
||||
from ..utils.tls_backport import match_hostname
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'using tls_backport')
|
||||
|
||||
try: # try to use SSLContext
|
||||
# noinspection PyUnresolvedReferences
|
||||
from ssl import create_default_context, Purpose # defined in Python 3.4 and Python 2.7.9
|
||||
use_ssl_context = True
|
||||
except ImportError:
|
||||
use_ssl_context = False
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'SSLContext unavailable')
|
||||
|
||||
from os import path
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
class Tls(object):
|
||||
"""
|
||||
tls/ssl configuration for Server object
|
||||
Starting from python 2.7.9 and python 3.4 uses the SSLContext object
|
||||
that tries to read the CAs defined at system level
|
||||
ca_certs_path and ca_certs_data are valid only when using SSLContext
|
||||
local_private_key_password is valid only when using SSLContext
|
||||
sni is the server name for Server Name Indication (when available)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
local_private_key_file=None,
|
||||
local_certificate_file=None,
|
||||
validate=ssl.CERT_NONE,
|
||||
version=None,
|
||||
ca_certs_file=None,
|
||||
valid_names=None,
|
||||
ca_certs_path=None,
|
||||
ca_certs_data=None,
|
||||
local_private_key_password=None,
|
||||
ciphers=None,
|
||||
sni=None):
|
||||
|
||||
if validate in [ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED]:
|
||||
self.validate = validate
|
||||
elif validate:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid validate parameter <%s>', validate)
|
||||
raise LDAPSSLConfigurationError('invalid validate parameter')
|
||||
if ca_certs_file and path.exists(ca_certs_file):
|
||||
self.ca_certs_file = ca_certs_file
|
||||
elif ca_certs_file:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid CA public key file <%s>', ca_certs_file)
|
||||
raise LDAPSSLConfigurationError('invalid CA public key file')
|
||||
else:
|
||||
self.ca_certs_file = None
|
||||
|
||||
if ca_certs_path and use_ssl_context and path.exists(ca_certs_path):
|
||||
self.ca_certs_path = ca_certs_path
|
||||
elif ca_certs_path and not use_ssl_context:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'cannot use CA public keys path, SSLContext not available')
|
||||
raise LDAPSSLNotSupportedError('cannot use CA public keys path, SSLContext not available')
|
||||
elif ca_certs_path:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid CA public keys path <%s>', ca_certs_path)
|
||||
raise LDAPSSLConfigurationError('invalid CA public keys path')
|
||||
else:
|
||||
self.ca_certs_path = None
|
||||
|
||||
if ca_certs_data and use_ssl_context:
|
||||
self.ca_certs_data = ca_certs_data
|
||||
elif ca_certs_data:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'cannot use CA data, SSLContext not available')
|
||||
raise LDAPSSLNotSupportedError('cannot use CA data, SSLContext not available')
|
||||
else:
|
||||
self.ca_certs_data = None
|
||||
|
||||
if local_private_key_password and use_ssl_context:
|
||||
self.private_key_password = local_private_key_password
|
||||
elif local_private_key_password:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'cannot use local private key password, SSLContext not available')
|
||||
raise LDAPSSLNotSupportedError('cannot use local private key password, SSLContext is not available')
|
||||
else:
|
||||
self.private_key_password = None
|
||||
|
||||
self.version = version
|
||||
self.private_key_file = local_private_key_file
|
||||
self.certificate_file = local_certificate_file
|
||||
self.valid_names = valid_names
|
||||
self.ciphers = ciphers
|
||||
self.sni = sni
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Tls: <%r>' % self)
|
||||
|
||||
def __str__(self):
|
||||
s = [
|
||||
'protocol: ' + str(self.version),
|
||||
'client private key: ' + ('present ' if self.private_key_file else 'not present'),
|
||||
'client certificate: ' + ('present ' if self.certificate_file else 'not present'),
|
||||
'private key password: ' + ('present ' if self.private_key_password else 'not present'),
|
||||
'CA certificates file: ' + ('present ' if self.ca_certs_file else 'not present'),
|
||||
'CA certificates path: ' + ('present ' if self.ca_certs_path else 'not present'),
|
||||
'CA certificates data: ' + ('present ' if self.ca_certs_data else 'not present'),
|
||||
'verify mode: ' + str(self.validate),
|
||||
'valid names: ' + str(self.valid_names),
|
||||
'ciphers: ' + str(self.ciphers),
|
||||
'sni: ' + str(self.sni)
|
||||
]
|
||||
return ' - '.join(s)
|
||||
|
||||
def __repr__(self):
|
||||
r = '' if self.private_key_file is None else ', local_private_key_file={0.private_key_file!r}'.format(self)
|
||||
r += '' if self.certificate_file is None else ', local_certificate_file={0.certificate_file!r}'.format(self)
|
||||
r += '' if self.validate is None else ', validate={0.validate!r}'.format(self)
|
||||
r += '' if self.version is None else ', version={0.version!r}'.format(self)
|
||||
r += '' if self.ca_certs_file is None else ', ca_certs_file={0.ca_certs_file!r}'.format(self)
|
||||
r += '' if self.ca_certs_path is None else ', ca_certs_path={0.ca_certs_path!r}'.format(self)
|
||||
r += '' if self.ca_certs_data is None else ', ca_certs_data={0.ca_certs_data!r}'.format(self)
|
||||
r += '' if self.ciphers is None else ', ciphers={0.ciphers!r}'.format(self)
|
||||
r += '' if self.sni is None else ', sni={0.sni!r}'.format(self)
|
||||
r = 'Tls(' + r[2:] + ')'
|
||||
return r
|
||||
|
||||
def wrap_socket(self, connection, do_handshake=False):
|
||||
"""
|
||||
Adds TLS to the connection socket
|
||||
"""
|
||||
if use_ssl_context:
|
||||
if self.version is None: # uses the default ssl context for reasonable security
|
||||
ssl_context = create_default_context(purpose=Purpose.SERVER_AUTH,
|
||||
cafile=self.ca_certs_file,
|
||||
capath=self.ca_certs_path,
|
||||
cadata=self.ca_certs_data)
|
||||
else: # code from create_default_context in the Python standard library 3.5.1, creates a ssl context with the specificd protocol version
|
||||
ssl_context = ssl.SSLContext(self.version)
|
||||
if self.ca_certs_file or self.ca_certs_path or self.ca_certs_data:
|
||||
ssl_context.load_verify_locations(self.ca_certs_file, self.ca_certs_path, self.ca_certs_data)
|
||||
elif self.validate != ssl.CERT_NONE:
|
||||
ssl_context.load_default_certs(Purpose.SERVER_AUTH)
|
||||
|
||||
if self.certificate_file:
|
||||
ssl_context.load_cert_chain(self.certificate_file, keyfile=self.private_key_file, password=self.private_key_password)
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = self.validate
|
||||
|
||||
if self.ciphers:
|
||||
try:
|
||||
ssl_context.set_ciphers(self.ciphers)
|
||||
except ssl.SSLError:
|
||||
pass
|
||||
|
||||
if self.sni:
|
||||
wrapped_socket = ssl_context.wrap_socket(connection.socket, server_side=False, do_handshake_on_connect=do_handshake, server_hostname=self.sni)
|
||||
else:
|
||||
wrapped_socket = ssl_context.wrap_socket(connection.socket, server_side=False, do_handshake_on_connect=do_handshake)
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'socket wrapped with SSL using SSLContext for <%s>', connection)
|
||||
else:
|
||||
if self.version is None and hasattr(ssl, 'PROTOCOL_SSLv23'):
|
||||
self.version = ssl.PROTOCOL_SSLv23
|
||||
if self.ciphers:
|
||||
try:
|
||||
|
||||
wrapped_socket = ssl.wrap_socket(connection.socket,
|
||||
keyfile=self.private_key_file,
|
||||
certfile=self.certificate_file,
|
||||
server_side=False,
|
||||
cert_reqs=self.validate,
|
||||
ssl_version=self.version,
|
||||
ca_certs=self.ca_certs_file,
|
||||
do_handshake_on_connect=do_handshake,
|
||||
ciphers=self.ciphers)
|
||||
except ssl.SSLError:
|
||||
raise
|
||||
except TypeError: # in python2.6 no ciphers argument is present, failback to self.ciphers=None
|
||||
self.ciphers = None
|
||||
|
||||
if not self.ciphers:
|
||||
wrapped_socket = ssl.wrap_socket(connection.socket,
|
||||
keyfile=self.private_key_file,
|
||||
certfile=self.certificate_file,
|
||||
server_side=False,
|
||||
cert_reqs=self.validate,
|
||||
ssl_version=self.version,
|
||||
ca_certs=self.ca_certs_file,
|
||||
do_handshake_on_connect=do_handshake)
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'socket wrapped with SSL for <%s>', connection)
|
||||
|
||||
if do_handshake and (self.validate == ssl.CERT_REQUIRED or self.validate == ssl.CERT_OPTIONAL):
|
||||
check_hostname(wrapped_socket, connection.server.host, self.valid_names)
|
||||
|
||||
connection.socket = wrapped_socket
|
||||
return
|
||||
|
||||
def start_tls(self, connection):
|
||||
if connection.server.ssl: # ssl already established at server level
|
||||
return False
|
||||
|
||||
if (connection.tls_started and not connection._executing_deferred) or connection.strategy._outstanding or connection.sasl_in_progress:
|
||||
# Per RFC 4513 (3.1.1)
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, "can't start tls because operations are in progress for <%s>", self)
|
||||
return False
|
||||
connection.starting_tls = True
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'starting tls for <%s>', connection)
|
||||
if not connection.strategy.sync:
|
||||
connection._awaiting_for_async_start_tls = True # some flaky servers (OpenLDAP) doesn't return the extended response name in response
|
||||
result = connection.extended('1.3.6.1.4.1.1466.20037')
|
||||
if not connection.strategy.sync:
|
||||
# asynchronous - _start_tls must be executed by the strategy
|
||||
response = connection.get_response(result)
|
||||
if response != (None, None):
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'tls started for <%s>', connection)
|
||||
return True
|
||||
else:
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'tls not started for <%s>', connection)
|
||||
return False
|
||||
else:
|
||||
if connection.result['description'] not in ['success']:
|
||||
# startTLS failed
|
||||
connection.last_error = 'startTLS failed - ' + str(connection.result['description'])
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, '%s for <%s>', connection.last_error, connection)
|
||||
raise LDAPStartTLSError(connection.last_error)
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'tls started for <%s>', connection)
|
||||
return self._start_tls(connection)
|
||||
|
||||
def _start_tls(self, connection):
|
||||
exc = None
|
||||
try:
|
||||
self.wrap_socket(connection, do_handshake=True)
|
||||
except Exception as e:
|
||||
connection.last_error = 'wrap socket error: ' + str(e)
|
||||
exc = e
|
||||
|
||||
connection.starting_tls = False
|
||||
|
||||
if exc:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'error <%s> wrapping socket for TLS in <%s>', connection.last_error, connection)
|
||||
raise start_tls_exception_factory(LDAPStartTLSError, exc)(connection.last_error)
|
||||
|
||||
if connection.usage:
|
||||
connection._usage.wrapped_sockets += 1
|
||||
|
||||
connection.tls_started = True
|
||||
return True
|
||||
|
||||
|
||||
def check_hostname(sock, server_name, additional_names):
|
||||
server_certificate = sock.getpeercert()
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'certificate found for %s: %s', sock, server_certificate)
|
||||
if additional_names:
|
||||
host_names = [server_name] + (additional_names if isinstance(additional_names, SEQUENCE_TYPES) else [additional_names])
|
||||
else:
|
||||
host_names = [server_name]
|
||||
|
||||
for host_name in host_names:
|
||||
if not host_name:
|
||||
continue
|
||||
elif host_name == '*':
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'certificate matches * wildcard')
|
||||
return # valid
|
||||
|
||||
try:
|
||||
match_hostname(server_certificate, host_name) # raise CertificateError if certificate doesn't match server name
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, 'certificate matches host name <%s>', host_name)
|
||||
return # valid
|
||||
except CertificateError as e:
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, str(e))
|
||||
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, "hostname doesn't match certificate")
|
||||
raise LDAPCertificateError("certificate %s doesn't match any name in %s " % (server_certificate, str(host_names)))
|
229
thesisenv/lib/python3.6/site-packages/ldap3/core/usage.py
Normal file
229
thesisenv/lib/python3.6/site-packages/ldap3/core/usage.py
Normal file
@ -0,0 +1,229 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.03.15
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from os import linesep
|
||||
|
||||
from .exceptions import LDAPMetricsError
|
||||
from ..utils.log import log, log_enabled, ERROR, BASIC
|
||||
|
||||
|
||||
class ConnectionUsage(object):
|
||||
"""
|
||||
Collect statistics on connection usage
|
||||
"""
|
||||
|
||||
def reset(self):
|
||||
self.open_sockets = 0
|
||||
self.closed_sockets = 0
|
||||
self.wrapped_sockets = 0
|
||||
self.bytes_transmitted = 0
|
||||
self.bytes_received = 0
|
||||
self.messages_transmitted = 0
|
||||
self.messages_received = 0
|
||||
self.operations = 0
|
||||
self.abandon_operations = 0
|
||||
self.add_operations = 0
|
||||
self.bind_operations = 0
|
||||
self.compare_operations = 0
|
||||
self.delete_operations = 0
|
||||
self.extended_operations = 0
|
||||
self.modify_operations = 0
|
||||
self.modify_dn_operations = 0
|
||||
self.search_operations = 0
|
||||
self.unbind_operations = 0
|
||||
self.referrals_received = 0
|
||||
self.referrals_followed = 0
|
||||
self.referrals_connections = 0
|
||||
self.restartable_failures = 0
|
||||
self.restartable_successes = 0
|
||||
self.servers_from_pool = 0
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'reset usage metrics')
|
||||
|
||||
def __init__(self):
|
||||
self.initial_connection_start_time = None
|
||||
self.open_socket_start_time = None
|
||||
self.connection_stop_time = None
|
||||
self.last_transmitted_time = None
|
||||
self.last_received_time = None
|
||||
self.open_sockets = 0
|
||||
self.closed_sockets = 0
|
||||
self.wrapped_sockets = 0
|
||||
self.bytes_transmitted = 0
|
||||
self.bytes_received = 0
|
||||
self.messages_transmitted = 0
|
||||
self.messages_received = 0
|
||||
self.operations = 0
|
||||
self.abandon_operations = 0
|
||||
self.add_operations = 0
|
||||
self.bind_operations = 0
|
||||
self.compare_operations = 0
|
||||
self.delete_operations = 0
|
||||
self.extended_operations = 0
|
||||
self.modify_operations = 0
|
||||
self.modify_dn_operations = 0
|
||||
self.search_operations = 0
|
||||
self.unbind_operations = 0
|
||||
self.referrals_received = 0
|
||||
self.referrals_followed = 0
|
||||
self.referrals_connections = 0
|
||||
self.restartable_failures = 0
|
||||
self.restartable_successes = 0
|
||||
self.servers_from_pool = 0
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'instantiated Usage object')
|
||||
|
||||
def __repr__(self):
|
||||
r = 'Connection Usage:' + linesep
|
||||
r += ' Time: [elapsed: ' + str(self.elapsed_time) + ']' + linesep
|
||||
r += ' Initial start time: ' + (str(self.initial_connection_start_time.isoformat()) if self.initial_connection_start_time else '') + linesep
|
||||
r += ' Open socket time: ' + (str(self.open_socket_start_time.isoformat()) if self.open_socket_start_time else '') + linesep
|
||||
r += ' Last transmitted time: ' + (str(self.last_transmitted_time.isoformat()) if self.last_transmitted_time else '') + linesep
|
||||
r += ' Last received time: ' + (str(self.last_received_time.isoformat()) if self.last_received_time else '') + linesep
|
||||
r += ' Close socket time: ' + (str(self.connection_stop_time.isoformat()) if self.connection_stop_time else '') + linesep
|
||||
r += ' Server:' + linesep
|
||||
r += ' Servers from pool: ' + str(self.servers_from_pool) + linesep
|
||||
r += ' Sockets open: ' + str(self.open_sockets) + linesep
|
||||
r += ' Sockets closed: ' + str(self.closed_sockets) + linesep
|
||||
r += ' Sockets wrapped: ' + str(self.wrapped_sockets) + linesep
|
||||
r += ' Bytes: ' + str(self.bytes_transmitted + self.bytes_received) + linesep
|
||||
r += ' Transmitted: ' + str(self.bytes_transmitted) + linesep
|
||||
r += ' Received: ' + str(self.bytes_received) + linesep
|
||||
r += ' Messages: ' + str(self.messages_transmitted + self.messages_received) + linesep
|
||||
r += ' Transmitted: ' + str(self.messages_transmitted) + linesep
|
||||
r += ' Received: ' + str(self.messages_received) + linesep
|
||||
r += ' Operations: ' + str(self.operations) + linesep
|
||||
r += ' Abandon: ' + str(self.abandon_operations) + linesep
|
||||
r += ' Bind: ' + str(self.bind_operations) + linesep
|
||||
r += ' Add: ' + str(self.add_operations) + linesep
|
||||
r += ' Compare: ' + str(self.compare_operations) + linesep
|
||||
r += ' Delete: ' + str(self.delete_operations) + linesep
|
||||
r += ' Extended: ' + str(self.extended_operations) + linesep
|
||||
r += ' Modify: ' + str(self.modify_operations) + linesep
|
||||
r += ' ModifyDn: ' + str(self.modify_dn_operations) + linesep
|
||||
r += ' Search: ' + str(self.search_operations) + linesep
|
||||
r += ' Unbind: ' + str(self.unbind_operations) + linesep
|
||||
r += ' Referrals: ' + linesep
|
||||
r += ' Received: ' + str(self.referrals_received) + linesep
|
||||
r += ' Followed: ' + str(self.referrals_followed) + linesep
|
||||
r += ' Connections: ' + str(self.referrals_connections) + linesep
|
||||
r += ' Restartable tries: ' + str(self.restartable_failures + self.restartable_successes) + linesep
|
||||
r += ' Failed restarts: ' + str(self.restartable_failures) + linesep
|
||||
r += ' Successful restarts: ' + str(self.restartable_successes) + linesep
|
||||
return r
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def __iadd__(self, other):
|
||||
if not isinstance(other, ConnectionUsage):
|
||||
raise LDAPMetricsError('unable to add to ConnectionUsage')
|
||||
|
||||
self.open_sockets += other.open_sockets
|
||||
self.closed_sockets += other.closed_sockets
|
||||
self.wrapped_sockets += other.wrapped_sockets
|
||||
self.bytes_transmitted += other.bytes_transmitted
|
||||
self.bytes_received += other.bytes_received
|
||||
self.messages_transmitted += other.messages_transmitted
|
||||
self.messages_received += other.messages_received
|
||||
self.operations += other.operations
|
||||
self.abandon_operations += other.abandon_operations
|
||||
self.add_operations += other.add_operations
|
||||
self.bind_operations += other.bind_operations
|
||||
self.compare_operations += other.compare_operations
|
||||
self.delete_operations += other.delete_operations
|
||||
self.extended_operations += other.extended_operations
|
||||
self.modify_operations += other.modify_operations
|
||||
self.modify_dn_operations += other.modify_dn_operations
|
||||
self.search_operations += other.search_operations
|
||||
self.unbind_operations += other.unbind_operations
|
||||
self.referrals_received += other.referrals_received
|
||||
self.referrals_followed += other.referrals_followed
|
||||
self.referrals_connections += other.referrals_connections
|
||||
self.restartable_failures += other.restartable_failures
|
||||
self.restartable_successes += other.restartable_successes
|
||||
self.servers_from_pool += other.servers_from_pool
|
||||
return self
|
||||
|
||||
def update_transmitted_message(self, message, length):
|
||||
self.last_transmitted_time = datetime.now()
|
||||
self.bytes_transmitted += length
|
||||
self.operations += 1
|
||||
self.messages_transmitted += 1
|
||||
if message['type'] == 'abandonRequest':
|
||||
self.abandon_operations += 1
|
||||
elif message['type'] == 'addRequest':
|
||||
self.add_operations += 1
|
||||
elif message['type'] == 'bindRequest':
|
||||
self.bind_operations += 1
|
||||
elif message['type'] == 'compareRequest':
|
||||
self.compare_operations += 1
|
||||
elif message['type'] == 'delRequest':
|
||||
self.delete_operations += 1
|
||||
elif message['type'] == 'extendedReq':
|
||||
self.extended_operations += 1
|
||||
elif message['type'] == 'modifyRequest':
|
||||
self.modify_operations += 1
|
||||
elif message['type'] == 'modDNRequest':
|
||||
self.modify_dn_operations += 1
|
||||
elif message['type'] == 'searchRequest':
|
||||
self.search_operations += 1
|
||||
elif message['type'] == 'unbindRequest':
|
||||
self.unbind_operations += 1
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'unable to collect usage for unknown message type <%s>', message['type'])
|
||||
raise LDAPMetricsError('unable to collect usage for unknown message type')
|
||||
|
||||
def update_received_message(self, length):
|
||||
self.last_received_time = datetime.now()
|
||||
self.bytes_received += length
|
||||
self.messages_received += 1
|
||||
|
||||
def start(self, reset=True):
|
||||
if reset:
|
||||
self.reset()
|
||||
self.open_socket_start_time = datetime.now()
|
||||
self.connection_stop_time = None
|
||||
if not self.initial_connection_start_time:
|
||||
self.initial_connection_start_time = self.open_socket_start_time
|
||||
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'start collecting usage metrics')
|
||||
|
||||
def stop(self):
|
||||
if self.open_socket_start_time:
|
||||
self.connection_stop_time = datetime.now()
|
||||
if log_enabled(BASIC):
|
||||
log(BASIC, 'stop collecting usage metrics')
|
||||
|
||||
@property
|
||||
def elapsed_time(self):
|
||||
if self.connection_stop_time:
|
||||
return self.connection_stop_time - self.open_socket_start_time
|
||||
else:
|
||||
return (datetime.now() - self.open_socket_start_time) if self.open_socket_start_time else timedelta(0)
|
289
thesisenv/lib/python3.6/site-packages/ldap3/extend/__init__.py
Normal file
289
thesisenv/lib/python3.6/site-packages/ldap3/extend/__init__.py
Normal file
@ -0,0 +1,289 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.04.28
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os import linesep
|
||||
|
||||
from .. import SUBTREE, DEREF_ALWAYS, ALL_ATTRIBUTES, DEREF_NEVER
|
||||
from .microsoft.dirSync import DirSync
|
||||
from .microsoft.modifyPassword import ad_modify_password
|
||||
from .microsoft.unlockAccount import ad_unlock_account
|
||||
from .microsoft.addMembersToGroups import ad_add_members_to_groups
|
||||
from .microsoft.removeMembersFromGroups import ad_remove_members_from_groups
|
||||
from .novell.partition_entry_count import PartitionEntryCount
|
||||
from .novell.replicaInfo import ReplicaInfo
|
||||
from .novell.listReplicas import ListReplicas
|
||||
from .novell.getBindDn import GetBindDn
|
||||
from .novell.nmasGetUniversalPassword import NmasGetUniversalPassword
|
||||
from .novell.nmasSetUniversalPassword import NmasSetUniversalPassword
|
||||
from .novell.startTransaction import StartTransaction
|
||||
from .novell.endTransaction import EndTransaction
|
||||
from .novell.addMembersToGroups import edir_add_members_to_groups
|
||||
from .novell.removeMembersFromGroups import edir_remove_members_from_groups
|
||||
from .novell.checkGroupsMemberships import edir_check_groups_memberships
|
||||
from .standard.whoAmI import WhoAmI
|
||||
from .standard.modifyPassword import ModifyPassword
|
||||
from .standard.PagedSearch import paged_search_generator, paged_search_accumulator
|
||||
from .standard.PersistentSearch import PersistentSearch
|
||||
|
||||
|
||||
class ExtendedOperationContainer(object):
|
||||
def __init__(self, connection):
|
||||
self._connection = connection
|
||||
|
||||
def __repr__(self):
|
||||
return linesep.join([' ' + element for element in dir(self) if element[0] != '_'])
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class StandardExtendedOperations(ExtendedOperationContainer):
|
||||
def who_am_i(self, controls=None):
|
||||
return WhoAmI(self._connection,
|
||||
controls).send()
|
||||
|
||||
def modify_password(self,
|
||||
user=None,
|
||||
old_password=None,
|
||||
new_password=None,
|
||||
hash_algorithm=None,
|
||||
salt=None,
|
||||
controls=None):
|
||||
|
||||
return ModifyPassword(self._connection,
|
||||
user,
|
||||
old_password,
|
||||
new_password,
|
||||
hash_algorithm,
|
||||
salt,
|
||||
controls).send()
|
||||
|
||||
def paged_search(self,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope=SUBTREE,
|
||||
dereference_aliases=DEREF_ALWAYS,
|
||||
attributes=None,
|
||||
size_limit=0,
|
||||
time_limit=0,
|
||||
types_only=False,
|
||||
get_operational_attributes=False,
|
||||
controls=None,
|
||||
paged_size=100,
|
||||
paged_criticality=False,
|
||||
generator=True):
|
||||
|
||||
if generator:
|
||||
return paged_search_generator(self._connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
types_only,
|
||||
get_operational_attributes,
|
||||
controls,
|
||||
paged_size,
|
||||
paged_criticality)
|
||||
else:
|
||||
return paged_search_accumulator(self._connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
types_only,
|
||||
get_operational_attributes,
|
||||
controls,
|
||||
paged_size,
|
||||
paged_criticality)
|
||||
|
||||
def persistent_search(self,
|
||||
search_base='',
|
||||
search_filter='(objectclass=*)',
|
||||
search_scope=SUBTREE,
|
||||
dereference_aliases=DEREF_NEVER,
|
||||
attributes=ALL_ATTRIBUTES,
|
||||
size_limit=0,
|
||||
time_limit=0,
|
||||
controls=None,
|
||||
changes_only=True,
|
||||
show_additions=True,
|
||||
show_deletions=True,
|
||||
show_modifications=True,
|
||||
show_dn_modifications=True,
|
||||
notifications=True,
|
||||
streaming=True,
|
||||
callback=None
|
||||
):
|
||||
events_type = 0
|
||||
if show_additions:
|
||||
events_type += 1
|
||||
if show_deletions:
|
||||
events_type += 2
|
||||
if show_modifications:
|
||||
events_type += 4
|
||||
if show_dn_modifications:
|
||||
events_type += 8
|
||||
|
||||
if callback:
|
||||
streaming = False
|
||||
return PersistentSearch(self._connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
controls,
|
||||
changes_only,
|
||||
events_type,
|
||||
notifications,
|
||||
streaming,
|
||||
callback)
|
||||
|
||||
|
||||
class NovellExtendedOperations(ExtendedOperationContainer):
|
||||
def get_bind_dn(self, controls=None):
|
||||
return GetBindDn(self._connection,
|
||||
controls).send()
|
||||
|
||||
def get_universal_password(self, user, controls=None):
|
||||
return NmasGetUniversalPassword(self._connection,
|
||||
user,
|
||||
controls).send()
|
||||
|
||||
def set_universal_password(self, user, new_password=None, controls=None):
|
||||
return NmasSetUniversalPassword(self._connection,
|
||||
user,
|
||||
new_password,
|
||||
controls).send()
|
||||
|
||||
def list_replicas(self, server_dn, controls=None):
|
||||
return ListReplicas(self._connection,
|
||||
server_dn,
|
||||
controls).send()
|
||||
|
||||
def partition_entry_count(self, partition_dn, controls=None):
|
||||
return PartitionEntryCount(self._connection,
|
||||
partition_dn,
|
||||
controls).send()
|
||||
|
||||
def replica_info(self, server_dn, partition_dn, controls=None):
|
||||
return ReplicaInfo(self._connection,
|
||||
server_dn,
|
||||
partition_dn,
|
||||
controls).send()
|
||||
|
||||
def start_transaction(self, controls=None):
|
||||
return StartTransaction(self._connection,
|
||||
controls).send()
|
||||
|
||||
def end_transaction(self, commit=True, controls=None): # attach the groupingControl to commit, None to abort transaction
|
||||
return EndTransaction(self._connection,
|
||||
commit,
|
||||
controls).send()
|
||||
|
||||
def add_members_to_groups(self, members, groups, fix=True, transaction=True):
|
||||
return edir_add_members_to_groups(self._connection,
|
||||
members_dn=members,
|
||||
groups_dn=groups,
|
||||
fix=fix,
|
||||
transaction=transaction)
|
||||
|
||||
def remove_members_from_groups(self, members, groups, fix=True, transaction=True):
|
||||
return edir_remove_members_from_groups(self._connection,
|
||||
members_dn=members,
|
||||
groups_dn=groups,
|
||||
fix=fix,
|
||||
transaction=transaction)
|
||||
|
||||
def check_groups_memberships(self, members, groups, fix=False, transaction=True):
|
||||
return edir_check_groups_memberships(self._connection,
|
||||
members_dn=members,
|
||||
groups_dn=groups,
|
||||
fix=fix,
|
||||
transaction=transaction)
|
||||
|
||||
|
||||
class MicrosoftExtendedOperations(ExtendedOperationContainer):
|
||||
def dir_sync(self,
|
||||
sync_base,
|
||||
sync_filter='(objectclass=*)',
|
||||
attributes=ALL_ATTRIBUTES,
|
||||
cookie=None,
|
||||
object_security=False,
|
||||
ancestors_first=True,
|
||||
public_data_only=False,
|
||||
incremental_values=True,
|
||||
max_length=2147483647,
|
||||
hex_guid=False):
|
||||
return DirSync(self._connection,
|
||||
sync_base=sync_base,
|
||||
sync_filter=sync_filter,
|
||||
attributes=attributes,
|
||||
cookie=cookie,
|
||||
object_security=object_security,
|
||||
ancestors_first=ancestors_first,
|
||||
public_data_only=public_data_only,
|
||||
incremental_values=incremental_values,
|
||||
max_length=max_length,
|
||||
hex_guid=hex_guid)
|
||||
|
||||
def modify_password(self, user, new_password, old_password=None, controls=None):
|
||||
return ad_modify_password(self._connection,
|
||||
user,
|
||||
new_password,
|
||||
old_password,
|
||||
controls)
|
||||
|
||||
def unlock_account(self, user):
|
||||
return ad_unlock_account(self._connection,
|
||||
user)
|
||||
|
||||
def add_members_to_groups(self, members, groups, fix=True):
|
||||
return ad_add_members_to_groups(self._connection,
|
||||
members_dn=members,
|
||||
groups_dn=groups,
|
||||
fix=fix)
|
||||
|
||||
def remove_members_from_groups(self, members, groups, fix=True):
|
||||
return ad_remove_members_from_groups(self._connection,
|
||||
members_dn=members,
|
||||
groups_dn=groups,
|
||||
fix=fix)
|
||||
|
||||
|
||||
class ExtendedOperationsRoot(ExtendedOperationContainer):
|
||||
def __init__(self, connection):
|
||||
ExtendedOperationContainer.__init__(self, connection) # calls super
|
||||
self.standard = StandardExtendedOperations(self._connection)
|
||||
self.novell = NovellExtendedOperations(self._connection)
|
||||
self.microsoft = MicrosoftExtendedOperations(self._connection)
|
@ -0,0 +1,81 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.12.26
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from ...core.exceptions import LDAPInvalidDnError
|
||||
from ... import SEQUENCE_TYPES, MODIFY_ADD, BASE, DEREF_NEVER
|
||||
|
||||
|
||||
def ad_add_members_to_groups(connection,
|
||||
members_dn,
|
||||
groups_dn,
|
||||
fix=True):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to add to groups
|
||||
:param groups_dn: the list of groups where members are to be added
|
||||
:param fix: checks for group existence and already assigned members
|
||||
:return: a boolean where True means that the operation was successful and False means an error has happened
|
||||
Establishes users-groups relations following the Active Directory rules: users are added to the member attribute of groups.
|
||||
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
|
||||
"""
|
||||
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
error = False
|
||||
for group in groups_dn:
|
||||
if fix: # checks for existance of group and for already assigned members
|
||||
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(group + ' not found')
|
||||
|
||||
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
|
||||
existing_members = [element.lower() for element in existing_members]
|
||||
else:
|
||||
existing_members = []
|
||||
|
||||
changes = dict()
|
||||
member_to_add = [element for element in members_dn if element.lower() not in existing_members]
|
||||
if member_to_add:
|
||||
changes['member'] = (MODIFY_ADD, member_to_add)
|
||||
if changes:
|
||||
result = connection.modify(group, changes)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
return not error # returns True if no error is raised in the LDAP operations
|
@ -0,0 +1,91 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.10.21
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...core.exceptions import LDAPExtensionError
|
||||
from ...protocol.microsoft import dir_sync_control, extended_dn_control, show_deleted_control
|
||||
from ... import SUBTREE, DEREF_NEVER
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class DirSync(object):
|
||||
def __init__(self,
|
||||
connection,
|
||||
sync_base,
|
||||
sync_filter,
|
||||
attributes,
|
||||
cookie,
|
||||
object_security,
|
||||
ancestors_first,
|
||||
public_data_only,
|
||||
incremental_values,
|
||||
max_length,
|
||||
hex_guid
|
||||
):
|
||||
self.connection = connection
|
||||
if self.connection.check_names and sync_base:
|
||||
self. base = safe_dn(sync_base)
|
||||
else:
|
||||
self.base = sync_base
|
||||
self.filter = sync_filter
|
||||
self.attributes = attributes
|
||||
self.cookie = cookie
|
||||
self.object_security = object_security
|
||||
self.ancestors_first = ancestors_first
|
||||
self.public_data_only = public_data_only
|
||||
self.incremental_values = incremental_values
|
||||
self.max_length = max_length
|
||||
self.hex_guid = hex_guid
|
||||
self.more_results = True
|
||||
|
||||
def loop(self):
|
||||
result = self.connection.search(search_base=self.base,
|
||||
search_filter=self.filter,
|
||||
search_scope=SUBTREE,
|
||||
attributes=self.attributes,
|
||||
dereference_aliases=DEREF_NEVER,
|
||||
controls=[dir_sync_control(criticality=True,
|
||||
object_security=self.object_security,
|
||||
ancestors_first=self.ancestors_first,
|
||||
public_data_only=self.public_data_only,
|
||||
incremental_values=self.incremental_values,
|
||||
max_length=self.max_length, cookie=self.cookie),
|
||||
extended_dn_control(criticality=False, hex_format=self.hex_guid),
|
||||
show_deleted_control(criticality=False)]
|
||||
)
|
||||
if not self.connection.strategy.sync:
|
||||
response, result = self.connection.get_response(result)
|
||||
else:
|
||||
response = self.connection.response
|
||||
result = self.connection.result
|
||||
|
||||
if result['description'] == 'success' and 'controls' in result and '1.2.840.113556.1.4.841' in result['controls']:
|
||||
self.more_results = result['controls']['1.2.840.113556.1.4.841']['value']['more_results']
|
||||
self.cookie = result['controls']['1.2.840.113556.1.4.841']['value']['cookie']
|
||||
return response
|
||||
elif 'controls' in result:
|
||||
raise LDAPExtensionError('Missing DirSync control in response from server')
|
||||
else:
|
||||
raise LDAPExtensionError('error %r in DirSync' % result)
|
||||
|
@ -0,0 +1,72 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.11.27
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from ... import MODIFY_REPLACE, MODIFY_DELETE, MODIFY_ADD
|
||||
from ...utils.log import log, log_enabled, PROTOCOL
|
||||
from ...core.results import RESULT_SUCCESS
|
||||
from ...utils.dn import safe_dn
|
||||
from ...utils.conv import to_unicode
|
||||
|
||||
|
||||
def ad_modify_password(connection, user_dn, new_password, old_password, controls=None):
|
||||
# old password must be None to reset password with sufficient privileges
|
||||
if connection.check_names:
|
||||
user_dn = safe_dn(user_dn)
|
||||
if str is bytes: # python2, converts to unicode
|
||||
new_password = to_unicode(new_password)
|
||||
if old_password:
|
||||
old_password = to_unicode(old_password)
|
||||
|
||||
encoded_new_password = ('"%s"' % new_password).encode('utf-16-le')
|
||||
|
||||
if old_password: # normal users must specify old and new password
|
||||
encoded_old_password = ('"%s"' % old_password).encode('utf-16-le')
|
||||
result = connection.modify(user_dn,
|
||||
{'unicodePwd': [(MODIFY_DELETE, [encoded_old_password]),
|
||||
(MODIFY_ADD, [encoded_new_password])]},
|
||||
controls)
|
||||
else: # admin users can reset password without sending the old one
|
||||
result = connection.modify(user_dn,
|
||||
{'unicodePwd': [(MODIFY_REPLACE, [encoded_new_password])]},
|
||||
controls)
|
||||
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
|
||||
# change successful, returns True
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
return True
|
||||
|
||||
# change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
|
||||
if connection.raise_exceptions:
|
||||
from ...core.exceptions import LDAPOperationResult
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'operation result <%s> for <%s>', result, connection)
|
||||
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
|
||||
|
||||
return False
|
@ -0,0 +1,93 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.12.26
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from ...core.exceptions import LDAPInvalidDnError
|
||||
from ... import SEQUENCE_TYPES, MODIFY_DELETE, BASE, DEREF_NEVER
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
def ad_remove_members_from_groups(connection,
|
||||
members_dn,
|
||||
groups_dn,
|
||||
fix):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to remove from groups
|
||||
:param groups_dn: the list of groups where members are to be removed
|
||||
:param fix: checks for group existence and existing members
|
||||
:return: a boolean where True means that the operation was successful and False means an error has happened
|
||||
Removes users-groups relations following the Activwe Directory rules: users are removed from groups' member attribute
|
||||
|
||||
"""
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
if connection.check_names: # builds new lists with sanitized dn
|
||||
safe_members_dn = []
|
||||
safe_groups_dn = []
|
||||
for member_dn in members_dn:
|
||||
safe_members_dn.append(safe_dn(member_dn))
|
||||
for group_dn in groups_dn:
|
||||
safe_groups_dn.append(safe_dn(group_dn))
|
||||
|
||||
members_dn = safe_members_dn
|
||||
groups_dn = safe_groups_dn
|
||||
|
||||
error = False
|
||||
|
||||
for group in groups_dn:
|
||||
if fix: # checks for existance of group and for already assigned members
|
||||
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(group + ' not found')
|
||||
|
||||
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
|
||||
else:
|
||||
existing_members = members_dn
|
||||
|
||||
existing_members = [element.lower() for element in existing_members]
|
||||
changes = dict()
|
||||
member_to_remove = [element for element in members_dn if element.lower() in existing_members]
|
||||
if member_to_remove:
|
||||
changes['member'] = (MODIFY_DELETE, member_to_remove)
|
||||
if changes:
|
||||
result = connection.modify(group, changes)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
return not error
|
@ -0,0 +1,56 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.11.01
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from ... import MODIFY_REPLACE
|
||||
from ...utils.log import log, log_enabled, PROTOCOL
|
||||
from ...core.results import RESULT_SUCCESS
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
def ad_unlock_account(connection, user_dn, controls=None):
|
||||
if connection.check_names:
|
||||
user_dn = safe_dn(user_dn)
|
||||
result = connection.modify(user_dn,
|
||||
{'lockoutTime': [(MODIFY_REPLACE, ['0'])]},
|
||||
controls)
|
||||
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
|
||||
# change successful, returns True
|
||||
if result['result'] == RESULT_SUCCESS:
|
||||
return True
|
||||
|
||||
# change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
|
||||
if connection.raise_exceptions:
|
||||
from ...core.exceptions import LDAPOperationResult
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'operation result <%s> for <%s>', result, connection)
|
||||
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
|
||||
|
||||
return result
|
@ -0,0 +1,153 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.04.16
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from ...core.exceptions import LDAPInvalidDnError
|
||||
from ... import SEQUENCE_TYPES, MODIFY_ADD, BASE, DEREF_NEVER
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
def edir_add_members_to_groups(connection,
|
||||
members_dn,
|
||||
groups_dn,
|
||||
fix,
|
||||
transaction):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to add to groups
|
||||
:param groups_dn: the list of groups where members are to be added
|
||||
:param fix: checks for inconsistences in the users-groups relation and fixes them
|
||||
:param transaction: activates an LDAP transaction
|
||||
:return: a boolean where True means that the operation was successful and False means an error has happened
|
||||
Establishes users-groups relations following the eDirectory rules: groups are added to securityEquals and groupMembership
|
||||
attributes in the member object while members are added to member and equivalentToMe attributes in the group object.
|
||||
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
|
||||
"""
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
transaction_control = None
|
||||
error = False
|
||||
|
||||
if connection.check_names: # builds new lists with sanitized dn
|
||||
safe_members_dn = []
|
||||
safe_groups_dn = []
|
||||
for member_dn in members_dn:
|
||||
safe_members_dn.append(safe_dn(member_dn))
|
||||
for group_dn in groups_dn:
|
||||
safe_groups_dn.append(safe_dn(group_dn))
|
||||
|
||||
members_dn = safe_members_dn
|
||||
groups_dn = safe_groups_dn
|
||||
|
||||
if transaction:
|
||||
transaction_control = connection.extend.novell.start_transaction()
|
||||
|
||||
if not error:
|
||||
for member in members_dn:
|
||||
if fix: # checks for existance of member and for already assigned groups
|
||||
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['securityEquals', 'groupMembership'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(member + ' not found')
|
||||
|
||||
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
|
||||
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
|
||||
existing_security_equals = [element.lower() for element in existing_security_equals]
|
||||
existing_group_membership = [element.lower() for element in existing_group_membership]
|
||||
else:
|
||||
existing_security_equals = []
|
||||
existing_group_membership = []
|
||||
changes = dict()
|
||||
security_equals_to_add = [element for element in groups_dn if element.lower() not in existing_security_equals]
|
||||
group_membership_to_add = [element for element in groups_dn if element.lower() not in existing_group_membership]
|
||||
if security_equals_to_add:
|
||||
changes['securityEquals'] = (MODIFY_ADD, security_equals_to_add)
|
||||
if group_membership_to_add:
|
||||
changes['groupMembership'] = (MODIFY_ADD, group_membership_to_add)
|
||||
if changes:
|
||||
result = connection.modify(member, changes, controls=[transaction_control] if transaction else None)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
if not error:
|
||||
for group in groups_dn:
|
||||
if fix: # checks for existance of group and for already assigned members
|
||||
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(group + ' not found')
|
||||
|
||||
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
|
||||
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
|
||||
existing_members = [element.lower() for element in existing_members]
|
||||
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
|
||||
else:
|
||||
existing_members = []
|
||||
existing_equivalent_to_me = []
|
||||
|
||||
changes = dict()
|
||||
member_to_add = [element for element in members_dn if element.lower() not in existing_members]
|
||||
equivalent_to_me_to_add = [element for element in members_dn if element.lower() not in existing_equivalent_to_me]
|
||||
if member_to_add:
|
||||
changes['member'] = (MODIFY_ADD, member_to_add)
|
||||
if equivalent_to_me_to_add:
|
||||
changes['equivalentToMe'] = (MODIFY_ADD, equivalent_to_me_to_add)
|
||||
if changes:
|
||||
result = connection.modify(group, changes, controls=[transaction_control] if transaction else None)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
if transaction:
|
||||
if error: # aborts transaction in case of error in the modify operations
|
||||
result = connection.extend.novell.end_transaction(commit=False, controls=[transaction_control])
|
||||
else:
|
||||
result = connection.extend.novell.end_transaction(commit=True, controls=[transaction_control])
|
||||
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
|
||||
return not error # returns True if no error is raised in the LDAP operations
|
@ -0,0 +1,172 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.05.14
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .addMembersToGroups import edir_add_members_to_groups
|
||||
from ...core.exceptions import LDAPInvalidDnError
|
||||
from ... import SEQUENCE_TYPES, BASE, DEREF_NEVER
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
def _check_members_have_memberships(connection,
|
||||
members_dn,
|
||||
groups_dn):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to add to groups
|
||||
:param groups_dn: the list of groups where members are to be added
|
||||
:return: two booleans. The first when True means that all members have membership in all groups, The second when True means that
|
||||
there are inconsistences in the securityEquals attribute
|
||||
Checks user's group membership.
|
||||
Raises LDAPInvalidDNError if member is not found in the DIT.
|
||||
"""
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
partial = False # True when a member has groupMembership but doesn't have securityEquals
|
||||
for member in members_dn:
|
||||
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['groupMembership', 'securityEquals'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success': # member not found in DIT
|
||||
raise LDAPInvalidDnError(member + ' not found')
|
||||
|
||||
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
|
||||
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
|
||||
existing_security_equals = [element.lower() for element in existing_security_equals]
|
||||
existing_group_membership = [element.lower() for element in existing_group_membership]
|
||||
|
||||
for group in groups_dn:
|
||||
if group.lower() not in existing_group_membership:
|
||||
return False, False
|
||||
if group.lower() not in existing_security_equals:
|
||||
partial = True
|
||||
|
||||
return True, partial
|
||||
|
||||
|
||||
def _check_groups_contain_members(connection,
|
||||
groups_dn,
|
||||
members_dn):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to add to groups
|
||||
:param groups_dn: the list of groups where members are to be added
|
||||
:return: two booleans. The first when True means that all members have membership in all groups, The second when True means that
|
||||
there are inconsistences in the EquivalentToMe attribute
|
||||
Checks if groups have members in their 'member' attribute.
|
||||
Raises LDAPInvalidDNError if member is not found in the DIT.
|
||||
"""
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
partial = False # True when a group has member but doesn't have equivalentToMe
|
||||
for group in groups_dn:
|
||||
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(group + ' not found')
|
||||
|
||||
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
|
||||
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
|
||||
existing_members = [element.lower() for element in existing_members]
|
||||
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
|
||||
for member in members_dn:
|
||||
if member.lower() not in existing_members:
|
||||
return False, False
|
||||
if member.lower() not in existing_equivalent_to_me:
|
||||
partial = True
|
||||
|
||||
return True, partial
|
||||
|
||||
|
||||
def edir_check_groups_memberships(connection,
|
||||
members_dn,
|
||||
groups_dn,
|
||||
fix,
|
||||
transaction):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to check
|
||||
:param groups_dn: the list of groups to check
|
||||
:param fix: checks for inconsistences in the users-groups relation and fixes them
|
||||
:param transaction: activates an LDAP transaction when fixing
|
||||
:return: a boolean where True means that the operation was successful and False means an error has happened
|
||||
Checks and fixes users-groups relations following the eDirectory rules: groups are checked against 'groupMembership'
|
||||
attribute in the member object while members are checked against 'member' attribute in the group object.
|
||||
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
|
||||
"""
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if connection.check_names: # builds new lists with sanitized dn
|
||||
safe_members_dn = []
|
||||
safe_groups_dn = []
|
||||
for member_dn in members_dn:
|
||||
safe_members_dn.append(safe_dn(member_dn))
|
||||
for group_dn in groups_dn:
|
||||
safe_groups_dn.append(safe_dn(group_dn))
|
||||
|
||||
members_dn = safe_members_dn
|
||||
groups_dn = safe_groups_dn
|
||||
|
||||
try:
|
||||
members_have_memberships, partial_member_security = _check_members_have_memberships(connection, members_dn, groups_dn)
|
||||
groups_contain_members, partial_group_security = _check_groups_contain_members(connection, groups_dn, members_dn)
|
||||
except LDAPInvalidDnError:
|
||||
return False
|
||||
|
||||
if not members_have_memberships and not groups_contain_members:
|
||||
return False
|
||||
|
||||
if fix: # fix any inconsistences
|
||||
if (members_have_memberships and not groups_contain_members) \
|
||||
or (groups_contain_members and not members_have_memberships) \
|
||||
or partial_group_security \
|
||||
or partial_member_security:
|
||||
|
||||
for member in members_dn:
|
||||
for group in groups_dn:
|
||||
edir_add_members_to_groups(connection, member, group, True, transaction)
|
||||
|
||||
return True
|
@ -0,0 +1,58 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.04.14
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.novell import EndGroupTypeRequestValue, EndGroupTypeResponseValue, Sequence
|
||||
from ...utils.asn1 import decoder
|
||||
|
||||
|
||||
class EndTransaction(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.103.2'
|
||||
self.response_name = '2.16.840.1.113719.1.27.103.2'
|
||||
self.request_value = EndGroupTypeRequestValue()
|
||||
self.asn1_spec = EndGroupTypeResponseValue()
|
||||
|
||||
def __init__(self, connection, commit=True, controls=None):
|
||||
if controls and len(controls) == 1:
|
||||
group_cookie = decoder.decode(controls[0][2], asn1Spec=Sequence())[0][0] # get the cookie from the built groupingControl
|
||||
else:
|
||||
group_cookie = None
|
||||
controls = None
|
||||
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
if group_cookie:
|
||||
self.request_value['endGroupCookie'] = group_cookie # transactionGroupingType
|
||||
if not commit:
|
||||
self.request_value['endGroupValue'] = '' # an empty endGroupValue means abort transaction
|
||||
|
||||
def populate_result(self):
|
||||
try:
|
||||
self.result['value'] = self.decoded_response['endGroupValue']
|
||||
except TypeError:
|
||||
self.result['value'] = None
|
||||
|
||||
def set_response(self):
|
||||
self.response_value = self.result
|
@ -0,0 +1,41 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.04.30
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...protocol.novell import Identity
|
||||
from ...extend.operation import ExtendedOperation
|
||||
|
||||
|
||||
class GetBindDn(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.100.31'
|
||||
self.response_name = '2.16.840.1.113719.1.27.100.32'
|
||||
self.response_attribute = 'identity'
|
||||
self.asn1_spec = Identity()
|
||||
|
||||
def populate_result(self):
|
||||
try:
|
||||
self.result['identity'] = str(self.decoded_response) if self.decoded_response else None
|
||||
except TypeError:
|
||||
self.result['identity'] = None
|
@ -0,0 +1,50 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.07.03
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.novell import ReplicaList
|
||||
from ...protocol.rfc4511 import LDAPDN
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class ListReplicas(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.100.19'
|
||||
self.response_name = '2.16.840.1.113719.1.27.100.20'
|
||||
self.request_value = LDAPDN()
|
||||
self.asn1_spec = ReplicaList()
|
||||
self.response_attribute = 'replicas'
|
||||
|
||||
def __init__(self, connection, server_dn, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
if connection.check_names:
|
||||
server_dn = safe_dn(server_dn)
|
||||
self.request_value = LDAPDN(server_dn)
|
||||
|
||||
def populate_result(self):
|
||||
try:
|
||||
self.result['replicas'] = str(self.decoded_response['replicaList']) if self.decoded_response['replicaList'] else None
|
||||
except TypeError:
|
||||
self.result['replicas'] = None
|
@ -0,0 +1,56 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.07.03
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.novell import NmasGetUniversalPasswordRequestValue, NmasGetUniversalPasswordResponseValue, NMAS_LDAP_EXT_VERSION
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class NmasGetUniversalPassword(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.39.42.100.13'
|
||||
self.response_name = '2.16.840.1.113719.1.39.42.100.14'
|
||||
self.request_value = NmasGetUniversalPasswordRequestValue()
|
||||
self.asn1_spec = NmasGetUniversalPasswordResponseValue()
|
||||
self.response_attribute = 'password'
|
||||
|
||||
def __init__(self, connection, user, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
|
||||
if connection.check_names:
|
||||
user = safe_dn(user)
|
||||
|
||||
self.request_value['nmasver'] = NMAS_LDAP_EXT_VERSION
|
||||
self.request_value['reqdn'] = user
|
||||
|
||||
def populate_result(self):
|
||||
if self.decoded_response:
|
||||
self.result['nmasver'] = int(self.decoded_response['nmasver'])
|
||||
self.result['error'] = int(self.decoded_response['err'])
|
||||
try:
|
||||
|
||||
self.result['password'] = str(self.decoded_response['passwd']) if self.decoded_response['passwd'].hasValue() else None
|
||||
except TypeError:
|
||||
self.result['password'] = None
|
@ -0,0 +1,52 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.07.03
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.novell import NmasSetUniversalPasswordRequestValue, NmasSetUniversalPasswordResponseValue, NMAS_LDAP_EXT_VERSION
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class NmasSetUniversalPassword(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.39.42.100.11'
|
||||
self.response_name = '2.16.840.1.113719.1.39.42.100.12'
|
||||
self.request_value = NmasSetUniversalPasswordRequestValue()
|
||||
self.asn1_spec = NmasSetUniversalPasswordResponseValue()
|
||||
self.response_attribute = 'password'
|
||||
|
||||
def __init__(self, connection, user, new_password, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
if connection.check_names and user:
|
||||
user = safe_dn(user)
|
||||
|
||||
self.request_value['nmasver'] = NMAS_LDAP_EXT_VERSION
|
||||
if user:
|
||||
self.request_value['reqdn'] = user
|
||||
if new_password:
|
||||
self.request_value['new_passwd'] = new_password
|
||||
|
||||
def populate_result(self):
|
||||
self.result['nmasver'] = int(self.decoded_response['nmasver'])
|
||||
self.result['error'] = int(self.decoded_response['err'])
|
@ -0,0 +1,57 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.08.05
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyasn1.type.univ import Integer
|
||||
|
||||
from ...core.exceptions import LDAPExtensionError
|
||||
from ..operation import ExtendedOperation
|
||||
from ...protocol.rfc4511 import LDAPDN
|
||||
from ...utils.asn1 import decoder
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class PartitionEntryCount(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.100.13'
|
||||
self.response_name = '2.16.840.1.113719.1.27.100.14'
|
||||
self.request_value = LDAPDN()
|
||||
self.response_attribute = 'entry_count'
|
||||
|
||||
def __init__(self, connection, partition_dn, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
if connection.check_names:
|
||||
partition_dn = safe_dn(partition_dn)
|
||||
self.request_value = LDAPDN(partition_dn)
|
||||
|
||||
def populate_result(self):
|
||||
substrate = self.decoded_response
|
||||
try:
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['entry_count'] = int(decoded)
|
||||
except Exception:
|
||||
raise LDAPExtensionError('unable to decode substrate')
|
||||
|
||||
if substrate:
|
||||
raise LDAPExtensionError('unknown substrate remaining')
|
@ -0,0 +1,156 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.04.17
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from ...core.exceptions import LDAPInvalidDnError
|
||||
from ... import SEQUENCE_TYPES, MODIFY_DELETE, BASE, DEREF_NEVER
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
def edir_remove_members_from_groups(connection,
|
||||
members_dn,
|
||||
groups_dn,
|
||||
fix,
|
||||
transaction):
|
||||
"""
|
||||
:param connection: a bound Connection object
|
||||
:param members_dn: the list of members to remove from groups
|
||||
:param groups_dn: the list of groups where members are to be removed
|
||||
:param fix: checks for inconsistences in the users-groups relation and fixes them
|
||||
:param transaction: activates an LDAP transaction
|
||||
:return: a boolean where True means that the operation was successful and False means an error has happened
|
||||
Removes users-groups relations following the eDirectory rules: groups are removed from securityEquals and groupMembership
|
||||
attributes in the member object while members are removed from member and equivalentToMe attributes in the group object.
|
||||
Raises LDAPInvalidDnError if members or groups are not found in the DIT.
|
||||
|
||||
"""
|
||||
if not isinstance(members_dn, SEQUENCE_TYPES):
|
||||
members_dn = [members_dn]
|
||||
|
||||
if not isinstance(groups_dn, SEQUENCE_TYPES):
|
||||
groups_dn = [groups_dn]
|
||||
|
||||
if connection.check_names: # builds new lists with sanitized dn
|
||||
safe_members_dn = []
|
||||
safe_groups_dn = []
|
||||
for member_dn in members_dn:
|
||||
safe_members_dn.append(safe_dn(member_dn))
|
||||
for group_dn in groups_dn:
|
||||
safe_groups_dn.append(safe_dn(group_dn))
|
||||
|
||||
members_dn = safe_members_dn
|
||||
groups_dn = safe_groups_dn
|
||||
|
||||
transaction_control = None
|
||||
error = False
|
||||
|
||||
if transaction:
|
||||
transaction_control = connection.extend.novell.start_transaction()
|
||||
|
||||
if not error:
|
||||
for member in members_dn:
|
||||
if fix: # checks for existance of member and for already assigned groups
|
||||
result = connection.search(member, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['securityEquals', 'groupMembership'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(member + ' not found')
|
||||
|
||||
existing_security_equals = response[0]['attributes']['securityEquals'] if 'securityEquals' in response[0]['attributes'] else []
|
||||
existing_group_membership = response[0]['attributes']['groupMembership'] if 'groupMembership' in response[0]['attributes'] else []
|
||||
else:
|
||||
existing_security_equals = groups_dn
|
||||
existing_group_membership = groups_dn
|
||||
existing_security_equals = [element.lower() for element in existing_security_equals]
|
||||
existing_group_membership = [element.lower() for element in existing_group_membership]
|
||||
|
||||
changes = dict()
|
||||
security_equals_to_remove = [element for element in groups_dn if element.lower() in existing_security_equals]
|
||||
group_membership_to_remove = [element for element in groups_dn if element.lower() in existing_group_membership]
|
||||
if security_equals_to_remove:
|
||||
changes['securityEquals'] = (MODIFY_DELETE, security_equals_to_remove)
|
||||
if group_membership_to_remove:
|
||||
changes['groupMembership'] = (MODIFY_DELETE, group_membership_to_remove)
|
||||
if changes:
|
||||
result = connection.modify(member, changes, controls=[transaction_control] if transaction else None)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
if not error:
|
||||
for group in groups_dn:
|
||||
if fix: # checks for existance of group and for already assigned members
|
||||
result = connection.search(group, '(objectclass=*)', BASE, dereference_aliases=DEREF_NEVER, attributes=['member', 'equivalentToMe'])
|
||||
|
||||
if not connection.strategy.sync:
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response, result = connection.response, connection.result
|
||||
|
||||
if not result['description'] == 'success':
|
||||
raise LDAPInvalidDnError(group + ' not found')
|
||||
|
||||
existing_members = response[0]['attributes']['member'] if 'member' in response[0]['attributes'] else []
|
||||
existing_equivalent_to_me = response[0]['attributes']['equivalentToMe'] if 'equivalentToMe' in response[0]['attributes'] else []
|
||||
else:
|
||||
existing_members = members_dn
|
||||
existing_equivalent_to_me = members_dn
|
||||
|
||||
existing_members = [element.lower() for element in existing_members]
|
||||
existing_equivalent_to_me = [element.lower() for element in existing_equivalent_to_me]
|
||||
|
||||
changes = dict()
|
||||
member_to_remove = [element for element in members_dn if element.lower() in existing_members]
|
||||
equivalent_to_me_to_remove = [element for element in members_dn if element.lower() in existing_equivalent_to_me]
|
||||
if member_to_remove:
|
||||
changes['member'] = (MODIFY_DELETE, member_to_remove)
|
||||
if equivalent_to_me_to_remove:
|
||||
changes['equivalentToMe'] = (MODIFY_DELETE, equivalent_to_me_to_remove)
|
||||
if changes:
|
||||
result = connection.modify(group, changes, controls=[transaction_control] if transaction else None)
|
||||
if not connection.strategy.sync:
|
||||
_, result = connection.get_response(result)
|
||||
else:
|
||||
result = connection.result
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
break
|
||||
|
||||
if transaction:
|
||||
if error: # aborts transaction in case of error in the modify operations
|
||||
result = connection.extend.novell.end_transaction(commit=False, controls=[transaction_control])
|
||||
else:
|
||||
result = connection.extend.novell.end_transaction(commit=True, controls=[transaction_control])
|
||||
|
||||
if result['description'] != 'success':
|
||||
error = True
|
||||
|
||||
return not error # return True if no error is raised in the LDAP operations
|
@ -0,0 +1,79 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.08.07
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pyasn1.type.univ import Integer
|
||||
|
||||
from ...core.exceptions import LDAPExtensionError
|
||||
from ...protocol.novell import LDAPDN, ReplicaInfoRequestValue
|
||||
from ..operation import ExtendedOperation
|
||||
from ...utils.asn1 import decoder
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class ReplicaInfo(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.100.17'
|
||||
self.response_name = '2.16.840.1.113719.1.27.100.18'
|
||||
# self.asn1_spec = ReplicaInfoResponseValue()
|
||||
self.request_value = ReplicaInfoRequestValue()
|
||||
self.response_attribute = 'partition_dn'
|
||||
|
||||
def __init__(self, connection, server_dn, partition_dn, controls=None):
|
||||
if connection.check_names:
|
||||
if server_dn:
|
||||
server_dn = safe_dn(server_dn)
|
||||
if partition_dn:
|
||||
partition_dn = safe_dn(partition_dn)
|
||||
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
self.request_value['server_dn'] = server_dn
|
||||
self.request_value['partition_dn'] = partition_dn
|
||||
|
||||
def populate_result(self):
|
||||
substrate = self.decoded_response
|
||||
try:
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['partition_id'] = int(decoded)
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['replica_state'] = int(decoded)
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['modification_time'] = datetime.utcfromtimestamp(int(decoded))
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['purge_time'] = datetime.utcfromtimestamp(int(decoded))
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['local_partition_id'] = int(decoded)
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=LDAPDN())
|
||||
self.result['partition_dn'] = str(decoded)
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['replica_type'] = int(decoded)
|
||||
decoded, substrate = decoder.decode(substrate, asn1Spec=Integer())
|
||||
self.result['flags'] = int(decoded)
|
||||
except Exception:
|
||||
raise LDAPExtensionError('unable to decode substrate')
|
||||
|
||||
if substrate:
|
||||
raise LDAPExtensionError('unknown substrate remaining')
|
@ -0,0 +1,56 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.04.14
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.novell import CreateGroupTypeRequestValue, CreateGroupTypeResponseValue, GroupingControlValue
|
||||
from ...protocol.controls import build_control
|
||||
|
||||
|
||||
class StartTransaction(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '2.16.840.1.113719.1.27.103.1'
|
||||
self.response_name = '2.16.840.1.113719.1.27.103.1'
|
||||
self.request_value = CreateGroupTypeRequestValue()
|
||||
self.asn1_spec = CreateGroupTypeResponseValue()
|
||||
|
||||
def __init__(self, connection, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
self.request_value['createGroupType'] = '2.16.840.1.113719.1.27.103.7' # transactionGroupingType
|
||||
|
||||
def populate_result(self):
|
||||
self.result['cookie'] = int(self.decoded_response['createGroupCookie'])
|
||||
try:
|
||||
self.result['value'] = self.decoded_response['createGroupValue']
|
||||
except TypeError:
|
||||
self.result['value'] = None
|
||||
|
||||
def set_response(self):
|
||||
try:
|
||||
grouping_cookie_value = GroupingControlValue()
|
||||
grouping_cookie_value['groupingCookie'] = self.result['cookie']
|
||||
self.response_value = build_control('2.16.840.1.113719.1.27.103.7', True, grouping_cookie_value, encode_control_value=True) # groupingControl
|
||||
except TypeError:
|
||||
self.response_value = None
|
||||
|
@ -0,0 +1,91 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.07.04
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.results import RESULT_SUCCESS
|
||||
from ..core.exceptions import LDAPExtensionError
|
||||
from ..utils.asn1 import decoder
|
||||
|
||||
|
||||
class ExtendedOperation(object):
|
||||
def __init__(self, connection, controls=None):
|
||||
self.connection = connection
|
||||
self.decoded_response = None
|
||||
self.result = None
|
||||
self.asn1_spec = None # if None the response_value is returned without encoding
|
||||
self.request_name = None
|
||||
self.response_name = None
|
||||
self.request_value = None
|
||||
self.response_value = None
|
||||
self.response_attribute = None
|
||||
self.controls = controls
|
||||
self.config()
|
||||
|
||||
def send(self):
|
||||
if self.connection.check_names and self.connection.server.info is not None and self.connection.server.info.supported_extensions is not None: # checks if extension is supported
|
||||
for request_name in self.connection.server.info.supported_extensions:
|
||||
if request_name[0] == self.request_name:
|
||||
break
|
||||
else:
|
||||
raise LDAPExtensionError('extension not in DSA list of supported extensions')
|
||||
|
||||
resp = self.connection.extended(self.request_name, self.request_value, self.controls)
|
||||
if not self.connection.strategy.sync:
|
||||
_, self.result = self.connection.get_response(resp)
|
||||
else:
|
||||
self.result = self.connection.result
|
||||
self.decode_response()
|
||||
self.populate_result()
|
||||
self.set_response()
|
||||
return self.response_value
|
||||
|
||||
def populate_result(self):
|
||||
pass
|
||||
|
||||
def decode_response(self):
|
||||
if not self.result:
|
||||
return None
|
||||
if self.result['result'] not in [RESULT_SUCCESS]:
|
||||
if self.connection.raise_exceptions:
|
||||
raise LDAPExtensionError('extended operation error: ' + self.result['description'] + ' - ' + self.result['message'])
|
||||
else:
|
||||
return None
|
||||
if not self.response_name or self.result['responseName'] == self.response_name:
|
||||
if self.result['responseValue']:
|
||||
if self.asn1_spec is not None:
|
||||
decoded, unprocessed = decoder.decode(self.result['responseValue'], asn1Spec=self.asn1_spec)
|
||||
if unprocessed:
|
||||
raise LDAPExtensionError('error decoding extended response value')
|
||||
self.decoded_response = decoded
|
||||
else:
|
||||
self.decoded_response = self.result['responseValue']
|
||||
else:
|
||||
raise LDAPExtensionError('invalid response name received')
|
||||
|
||||
def set_response(self):
|
||||
self.response_value = self.result[self.response_attribute] if self.result and self.response_attribute in self.result else None
|
||||
self.connection.response = self.response_value
|
||||
|
||||
def config(self):
|
||||
pass
|
@ -0,0 +1,125 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.07.08
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ... import SUBTREE, DEREF_ALWAYS
|
||||
from ...utils.dn import safe_dn
|
||||
from ...core.results import DO_NOT_RAISE_EXCEPTIONS, RESULT_SIZE_LIMIT_EXCEEDED
|
||||
from ...core.exceptions import LDAPOperationResult
|
||||
from ...utils.log import log, log_enabled, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED
|
||||
|
||||
|
||||
def paged_search_generator(connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope=SUBTREE,
|
||||
dereference_aliases=DEREF_ALWAYS,
|
||||
attributes=None,
|
||||
size_limit=0,
|
||||
time_limit=0,
|
||||
types_only=False,
|
||||
get_operational_attributes=False,
|
||||
controls=None,
|
||||
paged_size=100,
|
||||
paged_criticality=False):
|
||||
if connection.check_names and search_base:
|
||||
search_base = safe_dn(search_base)
|
||||
|
||||
responses = []
|
||||
cookie = True # performs search at least one time
|
||||
while cookie:
|
||||
result = connection.search(search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
types_only,
|
||||
get_operational_attributes,
|
||||
controls,
|
||||
paged_size,
|
||||
paged_criticality,
|
||||
None if cookie is True else cookie)
|
||||
|
||||
if not isinstance(result, bool):
|
||||
response, result = connection.get_response(result)
|
||||
else:
|
||||
response = connection.response
|
||||
result = connection.result
|
||||
|
||||
responses.extend(response)
|
||||
try:
|
||||
cookie = result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
|
||||
except KeyError:
|
||||
cookie = None
|
||||
|
||||
if result and result['result'] not in DO_NOT_RAISE_EXCEPTIONS:
|
||||
if log_enabled(PROTOCOL):
|
||||
log(PROTOCOL, 'paged search operation result <%s> for <%s>', result, connection)
|
||||
if result['result'] == RESULT_SIZE_LIMIT_EXCEEDED:
|
||||
while responses:
|
||||
yield responses.pop()
|
||||
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
|
||||
|
||||
while responses:
|
||||
yield responses.pop()
|
||||
|
||||
connection.response = None
|
||||
|
||||
|
||||
def paged_search_accumulator(connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope=SUBTREE,
|
||||
dereference_aliases=DEREF_ALWAYS,
|
||||
attributes=None,
|
||||
size_limit=0,
|
||||
time_limit=0,
|
||||
types_only=False,
|
||||
get_operational_attributes=False,
|
||||
controls=None,
|
||||
paged_size=100,
|
||||
paged_criticality=False):
|
||||
if connection.check_names and search_base:
|
||||
search_base = safe_dn(search_base)
|
||||
|
||||
responses = []
|
||||
for response in paged_search_generator(connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
types_only,
|
||||
get_operational_attributes,
|
||||
controls,
|
||||
paged_size,
|
||||
paged_criticality):
|
||||
responses.append(response)
|
||||
|
||||
connection.response = responses
|
||||
return responses
|
@ -0,0 +1,121 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.07.08
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2016 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try:
|
||||
from queue import Empty
|
||||
except ImportError: # Python 2
|
||||
# noinspection PyUnresolvedReferences
|
||||
from Queue import Empty
|
||||
|
||||
from ...core.exceptions import LDAPExtensionError
|
||||
from ...protocol.persistentSearch import persistent_search_control
|
||||
from ... import SEQUENCE_TYPES
|
||||
from ...utils.dn import safe_dn
|
||||
|
||||
|
||||
class PersistentSearch(object):
|
||||
def __init__(self,
|
||||
connection,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
controls,
|
||||
changes_only,
|
||||
events_type,
|
||||
notifications,
|
||||
streaming,
|
||||
callback
|
||||
):
|
||||
if connection.strategy.sync:
|
||||
raise LDAPExtensionError('Persistent Search needs an asynchronous streaming connection')
|
||||
|
||||
if connection.check_names and search_base:
|
||||
search_base = safe_dn(search_base)
|
||||
|
||||
self.connection = connection
|
||||
self.changes_only = changes_only
|
||||
self.notifications = notifications
|
||||
self.message_id = None
|
||||
self.base = search_base
|
||||
self.filter = search_filter
|
||||
self.scope = search_scope
|
||||
self.dereference_aliases = dereference_aliases
|
||||
self.attributes = attributes
|
||||
self.size_limit = size_limit
|
||||
self.time_limit = time_limit
|
||||
self.connection.strategy.streaming = streaming
|
||||
if callback and callable(callback):
|
||||
self.connection.strategy.callback = callback
|
||||
elif callback:
|
||||
raise LDAPExtensionError('callback is not callable')
|
||||
|
||||
if not isinstance(controls, SEQUENCE_TYPES):
|
||||
self.controls = []
|
||||
else:
|
||||
self.controls = controls
|
||||
|
||||
self.controls.append(persistent_search_control(events_type, changes_only, notifications))
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
if self.message_id: # persistent search already started
|
||||
return
|
||||
|
||||
if not self.connection.bound:
|
||||
self.connection.bind()
|
||||
|
||||
with self.connection.strategy.async_lock:
|
||||
self.message_id = self.connection.search(search_base=self.base,
|
||||
search_filter=self.filter,
|
||||
search_scope=self.scope,
|
||||
dereference_aliases=self.dereference_aliases,
|
||||
attributes=self.attributes,
|
||||
size_limit=self.size_limit,
|
||||
time_limit=self.time_limit,
|
||||
controls=self.controls)
|
||||
self.connection.strategy.persistent_search_message_id = self.message_id
|
||||
|
||||
def stop(self):
|
||||
self.connection.abandon(self.message_id)
|
||||
self.connection.unbind()
|
||||
if self.message_id in self.connection.strategy._responses:
|
||||
del self.connection.strategy._responses[self.message_id]
|
||||
if hasattr(self.connection.strategy, '_requests') and self.message_id in self.connection.strategy._requests: # asynchronous strategy has a dict of request that could be returned by get_response()
|
||||
del self.connection.strategy._requests[self.message_id]
|
||||
self.connection.strategy.persistent_search_message_id = None
|
||||
self.message_id = None
|
||||
|
||||
def next(self):
|
||||
if not self.connection.strategy.streaming and not self.connection.strategy.callback:
|
||||
try:
|
||||
return self.connection.strategy.events.get_nowait()
|
||||
except Empty:
|
||||
return None
|
||||
|
||||
raise LDAPExtensionError('Persistent search is not accumulating events in queue')
|
@ -0,0 +1,72 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.04.30
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ... import HASHED_NONE
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...protocol.rfc3062 import PasswdModifyRequestValue, PasswdModifyResponseValue
|
||||
from ...utils.hashed import hashed
|
||||
from ...protocol.sasl.sasl import validate_simple_password
|
||||
from ...utils.dn import safe_dn
|
||||
from ...core.results import RESULT_SUCCESS
|
||||
|
||||
# implements RFC3062
|
||||
|
||||
|
||||
class ModifyPassword(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '1.3.6.1.4.1.4203.1.11.1'
|
||||
self.request_value = PasswdModifyRequestValue()
|
||||
self.asn1_spec = PasswdModifyResponseValue()
|
||||
self.response_attribute = 'new_password'
|
||||
|
||||
def __init__(self, connection, user=None, old_password=None, new_password=None, hash_algorithm=None, salt=None, controls=None):
|
||||
ExtendedOperation.__init__(self, connection, controls) # calls super __init__()
|
||||
if user:
|
||||
if connection.check_names:
|
||||
user = safe_dn(user)
|
||||
self.request_value['userIdentity'] = user
|
||||
if old_password:
|
||||
if not isinstance(old_password, bytes): # bytes are returned raw, as per RFC (4.2)
|
||||
old_password = validate_simple_password(old_password, True)
|
||||
self.request_value['oldPasswd'] = old_password
|
||||
if new_password:
|
||||
if not isinstance(new_password, bytes): # bytes are returned raw, as per RFC (4.2)
|
||||
new_password = validate_simple_password(new_password, True)
|
||||
if hash_algorithm is None or hash_algorithm == HASHED_NONE:
|
||||
self.request_value['newPasswd'] = new_password
|
||||
else:
|
||||
self.request_value['newPasswd'] = hashed(hash_algorithm, new_password, salt)
|
||||
|
||||
def populate_result(self):
|
||||
try:
|
||||
self.result[self.response_attribute] = str(self.decoded_response['genPasswd'])
|
||||
except TypeError: # optional field can be absent, so returns True if operation is successful else False
|
||||
if self.result['result'] == RESULT_SUCCESS:
|
||||
self.result[self.response_attribute] = True
|
||||
else: # change was not successful, raises exception if raise_exception = True in connection or returns the operation result, error code is in result['result']
|
||||
self.result[self.response_attribute] = False
|
||||
if not self.connection.raise_exceptions:
|
||||
from ...core.exceptions import LDAPOperationResult
|
||||
raise LDAPOperationResult(result=self.result['result'], description=self.result['description'], dn=self.result['dn'], message=self.result['message'], response_type=self.result['type'])
|
@ -0,0 +1,40 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.04.30
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# implements RFC4532
|
||||
|
||||
from ...extend.operation import ExtendedOperation
|
||||
from ...utils.conv import to_unicode
|
||||
|
||||
class WhoAmI(ExtendedOperation):
|
||||
def config(self):
|
||||
self.request_name = '1.3.6.1.4.1.4203.1.11.3'
|
||||
self.response_attribute = 'authzid'
|
||||
|
||||
def populate_result(self):
|
||||
try:
|
||||
self.result['authzid'] = to_unicode(self.decoded_response) if self.decoded_response else None
|
||||
except TypeError:
|
||||
self.result['authzid'] = self.decoded_response if self.decoded_response else None
|
@ -0,0 +1,36 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2013.05.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..protocol.rfc4511 import AbandonRequest, MessageID
|
||||
|
||||
|
||||
def abandon_operation(msg_id):
|
||||
# AbandonRequest ::= [APPLICATION 16] MessageID
|
||||
request = AbandonRequest(MessageID(msg_id))
|
||||
return request
|
||||
|
||||
|
||||
def abandon_request_to_dict(request):
|
||||
return {'messageId': str(request)}
|
72
thesisenv/lib/python3.6/site-packages/ldap3/operation/add.py
Normal file
72
thesisenv/lib/python3.6/site-packages/ldap3/operation/add.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2013.05.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .. import SEQUENCE_TYPES
|
||||
from ..protocol.rfc4511 import AddRequest, LDAPDN, AttributeList, Attribute, AttributeDescription, ResultCode, Vals
|
||||
from ..protocol.convert import referrals_to_list, attributes_to_dict, validate_attribute_value, prepare_for_sending
|
||||
|
||||
|
||||
def add_operation(dn,
|
||||
attributes,
|
||||
auto_encode,
|
||||
schema=None,
|
||||
validator=None,
|
||||
check_names=False):
|
||||
# AddRequest ::= [APPLICATION 8] SEQUENCE {
|
||||
# entry LDAPDN,
|
||||
# attributes AttributeList }
|
||||
#
|
||||
# attributes is a dictionary in the form 'attribute': ['val1', 'val2', 'valN']
|
||||
attribute_list = AttributeList()
|
||||
for pos, attribute in enumerate(attributes):
|
||||
attribute_list[pos] = Attribute()
|
||||
attribute_list[pos]['type'] = AttributeDescription(attribute)
|
||||
vals = Vals() # changed from ValsAtLeast1() for allowing empty member value in groups
|
||||
if isinstance(attributes[attribute], SEQUENCE_TYPES):
|
||||
for index, value in enumerate(attributes[attribute]):
|
||||
vals.setComponentByPosition(index, prepare_for_sending(validate_attribute_value(schema, attribute, value, auto_encode, validator, check_names)))
|
||||
else:
|
||||
vals.setComponentByPosition(0, prepare_for_sending(validate_attribute_value(schema, attribute, attributes[attribute], auto_encode, validator, check_names)))
|
||||
|
||||
attribute_list[pos]['vals'] = vals
|
||||
|
||||
request = AddRequest()
|
||||
request['entry'] = LDAPDN(dn)
|
||||
request['attributes'] = attribute_list
|
||||
|
||||
return request
|
||||
|
||||
|
||||
def add_request_to_dict(request):
|
||||
return {'entry': str(request['entry']),
|
||||
'attributes': attributes_to_dict(request['attributes'])}
|
||||
|
||||
|
||||
def add_response_to_dict(response):
|
||||
return {'result': int(response['resultCode']),
|
||||
'description': ResultCode().getNamedValues().getName(response['resultCode']),
|
||||
'dn': str(response['matchedDN']),
|
||||
'message': str(response['diagnosticMessage']),
|
||||
'referrals': referrals_to_list(response['referral'])}
|
160
thesisenv/lib/python3.6/site-packages/ldap3/operation/bind.py
Normal file
160
thesisenv/lib/python3.6/site-packages/ldap3/operation/bind.py
Normal file
@ -0,0 +1,160 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2013.05.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2018 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# ldap3 is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .. import SIMPLE, ANONYMOUS, SASL, STRING_TYPES
|
||||
from ..core.results import RESULT_CODES
|
||||
from ..core.exceptions import LDAPUserNameIsMandatoryError, LDAPPasswordIsMandatoryError, LDAPUnknownAuthenticationMethodError, LDAPUserNameNotAllowedError
|
||||
from ..protocol.sasl.sasl import validate_simple_password
|
||||
from ..protocol.rfc4511 import Version, AuthenticationChoice, Simple, BindRequest, ResultCode, SaslCredentials, BindResponse, \
|
||||
LDAPDN, LDAPString, Referral, ServerSaslCreds, SicilyPackageDiscovery, SicilyNegotiate, SicilyResponse
|
||||
from ..protocol.convert import authentication_choice_to_dict, referrals_to_list
|
||||
from ..utils.conv import to_unicode, to_raw
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def bind_operation(version,
|
||||
authentication,
|
||||
name='',
|
||||
password=None,
|
||||
sasl_mechanism=None,
|
||||
sasl_credentials=None,
|
||||
auto_encode=False):
|
||||
# BindRequest ::= [APPLICATION 0] SEQUENCE {
|
||||
# version INTEGER (1 .. 127),
|
||||
# name LDAPDN,
|
||||
# authentication AuthenticationChoice }
|
||||
request = BindRequest()
|
||||
request['version'] = Version(version)
|
||||
if name is None:
|
||||
name = ''
|
||||
if isinstance(name, STRING_TYPES):
|
||||
request['name'] = to_unicode(name) if auto_encode else name
|
||||
if authentication == SIMPLE:
|
||||
if not name:
|
||||
raise LDAPUserNameIsMandatoryError('user name is mandatory in simple bind')
|
||||
if password:
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('simple', Simple(validate_simple_password(password)))
|
||||
else:
|
||||
raise LDAPPasswordIsMandatoryError('password is mandatory in simple bind')
|
||||
elif authentication == SASL:
|
||||
sasl_creds = SaslCredentials()
|
||||
sasl_creds['mechanism'] = sasl_mechanism
|
||||
if sasl_credentials is not None:
|
||||
sasl_creds['credentials'] = sasl_credentials
|
||||
# else:
|
||||
# sasl_creds['credentials'] = None
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('sasl', sasl_creds)
|
||||
elif authentication == ANONYMOUS:
|
||||
if name:
|
||||
raise LDAPUserNameNotAllowedError('user name not allowed in anonymous bind')
|
||||
request['name'] = ''
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('simple', Simple(''))
|
||||
elif authentication == 'SICILY_PACKAGE_DISCOVERY': # https://msdn.microsoft.com/en-us/library/cc223501.aspx
|
||||
request['name'] = ''
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('sicilyPackageDiscovery', SicilyPackageDiscovery(''))
|
||||
elif authentication == 'SICILY_NEGOTIATE_NTLM': # https://msdn.microsoft.com/en-us/library/cc223501.aspx
|
||||
request['name'] = 'NTLM'
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('sicilyNegotiate', SicilyNegotiate(name.create_negotiate_message())) # ntlm client in self.name
|
||||
elif authentication == 'SICILY_RESPONSE_NTLM': # https://msdn.microsoft.com/en-us/library/cc223501.aspx
|
||||
name.parse_challenge_message(password) # server_creds returned by server in password
|
||||
server_creds = name.create_authenticate_message()
|
||||
if server_creds:
|
||||
request['name'] = ''
|
||||
request['authentication'] = AuthenticationChoice().setComponentByName('sicilyResponse', SicilyResponse(server_creds))
|
||||
else:
|
||||
request = None
|
||||
else:
|
||||
raise LDAPUnknownAuthenticationMethodError('unknown authentication method')
|
||||
|
||||
return request
|
||||
|
||||
|
||||
def bind_request_to_dict(request):
|
||||
return {'version': int(request['version']),
|
||||
'name': str(request['name']),
|
||||
'authentication': authentication_choice_to_dict(request['authentication'])}
|
||||
|
||||
|
||||
def bind_response_operation(result_code,
|
||||
matched_dn='',
|
||||
diagnostic_message='',
|
||||
referral=None,
|
||||
server_sasl_credentials=None):
|
||||
# BindResponse ::= [APPLICATION 1] SEQUENCE {
|
||||
# COMPONENTS OF LDAPResult,
|
||||
# serverSaslCreds [7] OCTET STRING OPTIONAL }
|
||||
response = BindResponse()
|
||||
response['resultCode'] = ResultCode(result_code)
|
||||
response['matchedDN'] = LDAPDN(matched_dn)
|
||||
response['diagnosticMessage'] = LDAPString(diagnostic_message)
|
||||
if referral:
|
||||
response['referral'] = Referral(referral)
|
||||
|
||||
if server_sasl_credentials:
|
||||
response['serverSaslCreds'] = ServerSaslCreds(server_sasl_credentials)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def bind_response_to_dict(response):
|
||||
return {'result': int(response['resultCode']),
|
||||
'description': ResultCode().getNamedValues().getName(response['resultCode']),
|
||||
'dn': str(response['matchedDN']),
|
||||
'message': str(response['diagnosticMessage']),
|
||||
'referrals': referrals_to_list(response['referral']),
|
||||
'saslCreds': bytes(response['serverSaslCreds']) if response['serverSaslCreds'] is not None and response['serverSaslCreds'].hasValue() else None}
|
||||
|
||||
|
||||
def sicily_bind_response_to_dict(response):
|
||||
return {'result': int(response['resultCode']),
|
||||
'description': ResultCode().getNamedValues().getName(response['resultCode']),
|
||||
'server_creds': bytes(response['matchedDN']),
|
||||
'error_message': str(response['diagnosticMessage'])}
|
||||
|
||||
|
||||
def bind_response_to_dict_fast(response):
|
||||
response_dict = dict()
|
||||
response_dict['result'] = int(response[0][3]) # resultCode
|
||||
response_dict['description'] = RESULT_CODES[response_dict['result']]
|
||||
response_dict['dn'] = to_unicode(response[1][3], from_server=True) # matchedDN
|
||||
response_dict['message'] = to_unicode(response[2][3], from_server=True) # diagnosticMessage
|
||||
response_dict['referrals'] = None # referrals
|
||||
response_dict['saslCreds'] = None # saslCreds
|
||||
for r in response[3:]:
|
||||
if r[2] == 3: # referrals
|
||||
response_dict['referrals'] = referrals_to_list(r[3]) # referrals
|
||||
else:
|
||||
response_dict['saslCreds'] = bytes(r[3]) # saslCreds
|
||||
|
||||
return response_dict
|
||||
|
||||
|
||||
def sicily_bind_response_to_dict_fast(response):
|
||||
response_dict = dict()
|
||||
response_dict['result'] = int(response[0][3]) # resultCode
|
||||
response_dict['description'] = RESULT_CODES[response_dict['result']]
|
||||
response_dict['server_creds'] = bytes(response[1][3]) # server_creds
|
||||
response_dict['error_message'] = to_unicode(response[2][3], from_server=True) # error_message
|
||||
|
||||
return response_dict
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user