2013-06-07 12:13:03 +02:00
#!/usr/bin/python
2019-06-13 19:15:13 +02:00
import copy
import datetime
2019-06-13 19:59:42 +02:00
import dateutil . parser
import dateutil . tz
2013-06-07 12:13:03 +02:00
import ldap
import ldap . modlist as modlist
import logging
2019-06-13 19:15:13 +02:00
import pytz
2013-06-07 12:13:03 +02:00
class LdapServer ( object ) :
uri = None
dn = None
pwd = None
v2 = None
con = 0
2018-12-12 17:11:23 +01:00
def __init__ ( self , uri , dn = None , pwd = None , v2 = None , raiseOnError = False , logger = False ) :
2013-06-07 12:13:03 +02:00
self . uri = uri
self . dn = dn
self . pwd = pwd
2014-06-25 19:16:18 +02:00
self . raiseOnError = raiseOnError
2013-06-07 12:13:03 +02:00
if v2 :
self . v2 = True
2018-12-12 17:11:23 +01:00
if logger :
self . logger = logger
else :
self . logger = logging . getLogger ( )
2013-06-07 12:13:03 +02:00
2014-06-25 19:16:18 +02:00
def _error ( self , error , level = logging . WARNING ) :
if self . raiseOnError :
2018-02-13 15:30:38 +01:00
raise LdapServerException ( error )
2014-06-25 19:16:18 +02:00
else :
2020-09-23 15:53:58 +02:00
self . logger . log ( level , error )
2014-06-25 19:16:18 +02:00
2013-06-07 12:13:03 +02:00
def connect ( self ) :
if self . con == 0 :
try :
con = ldap . initialize ( self . uri )
if self . v2 :
con . protocol_version = ldap . VERSION2
else :
con . protocol_version = ldap . VERSION3
if self . dn :
con . simple_bind_s ( self . dn , self . pwd )
self . con = con
2014-06-25 19:16:18 +02:00
return True
2013-06-07 12:13:03 +02:00
except ldap . LDAPError , e :
2014-06-25 19:16:18 +02:00
self . _error ( ' LdapServer - Error connecting and binding to LDAP server : %s ' % e , logging . CRITICAL )
return False
return True
2013-06-07 12:13:03 +02:00
2019-04-19 09:53:44 +02:00
def get_scope ( self , scope ) :
if scope == ' base ' :
return ldap . SCOPE_BASE
elif scope == ' one ' :
return ldap . SCOPE_ONELEVEL
elif scope == ' sub ' :
return ldap . SCOPE_SUBTREE
raise Exception ( " Unknown LDAP scope ' %s ' " % scope )
2020-12-14 20:56:43 +01:00
def search ( self , basedn , filterstr = None , attrs = None , sizelimit = 0 , scope = None ) :
res_id = self . con . search (
basedn ,
self . get_scope ( scope if scope else ' sub ' ) ,
filterstr if filterstr else ' (objectClass=*) ' ,
attrs if attrs else [ ]
)
2013-06-07 12:13:03 +02:00
ret = { }
2020-12-14 20:56:43 +01:00
c = 0
while True :
2013-06-07 12:13:03 +02:00
res_type , res_data = self . con . result ( res_id , 0 )
2020-12-14 20:56:43 +01:00
if res_data == [ ] or ( sizelimit and c > sizelimit ) :
2013-06-07 12:13:03 +02:00
break
else :
if res_type == ldap . RES_SEARCH_ENTRY :
2020-12-14 20:56:43 +01:00
ret [ res_data [ 0 ] [ 0 ] ] = res_data [ 0 ] [ 1 ]
c + = 1
2013-06-07 12:13:03 +02:00
return ret
2020-12-14 20:56:43 +01:00
def get_object ( self , dn , filterstr = None , attrs = None ) :
result = self . search ( dn , filterstr = filterstr , scope = ' base ' , attrs = attrs )
return result [ dn ] if dn in result else None
2013-06-07 12:13:03 +02:00
def add_object ( self , dn , attrs ) :
ldif = modlist . addModlist ( attrs )
try :
2018-12-12 17:11:23 +01:00
self . logger . debug ( " LdapServer - Add %s " % dn )
2013-06-07 12:13:03 +02:00
self . con . add_s ( dn , ldif )
return True
except ldap . LDAPError , e :
2020-09-23 15:53:58 +02:00
self . _error ( " LdapServer - Error adding %s : %s " % ( dn , e ) , logging . ERROR )
2013-06-07 12:13:03 +02:00
return False
2018-02-16 10:52:30 +01:00
def update_object ( self , dn , old , new , ignore_attrs = [ ] ) :
ldif = modlist . modifyModlist ( old , new , ignore_attr_types = ignore_attrs )
2013-06-07 12:13:03 +02:00
if ldif == [ ] :
return True
try :
self . con . modify_s ( dn , ldif )
return True
except ldap . LDAPError , e :
2020-09-23 15:53:58 +02:00
self . _error ( " LdapServer - Error updating %s : %s \n Old : %s \n New : %s " % ( dn , e , old , new ) , logging . ERROR )
2013-06-07 12:13:03 +02:00
return False
2019-04-19 09:55:14 +02:00
def update_need ( self , old , new , ignore_attrs = [ ] ) :
ldif = modlist . modifyModlist ( old , new , ignore_attr_types = ignore_attrs )
if ldif == [ ] :
return False
return True
2020-09-23 15:55:53 +02:00
def get_changes ( self , old , new , ignore_attrs = [ ] ) :
return modlist . modifyModlist ( old , new , ignore_attr_types = ignore_attrs )
def format_changes ( self , old , new , ignore_attrs = [ ] , prefix = ' ' ) :
msg = [ ]
for ( op , attr , val ) in modlist . modifyModlist ( old , new , ignore_attr_types = ignore_attrs ) :
if op == ldap . MOD_ADD :
op = ' ADD '
elif op == ldap . MOD_DELETE :
op = ' DELETE '
elif op == ldap . MOD_REPLACE :
op = ' REPLACE '
else :
op = ' UNKNOWN (= %s ) ' % op
if val is None and op == ' DELETE ' :
msg . append ( ' %s - %s %s ' % ( prefix , op , attr ) )
else :
msg . append ( ' %s - %s %s : %s ' % ( prefix , op , attr , val ) )
return ' \n ' . join ( msg )
2015-04-01 17:08:26 +02:00
def rename_object ( self , dn , new_rdn ) :
try :
2018-12-12 17:11:23 +01:00
self . logger . debug ( " LdapServer - Rename %s in %s " % ( dn , new_rdn ) )
2015-04-01 17:08:26 +02:00
self . con . rename_s ( dn , new_rdn )
return True
except ldap . LDAPError , e :
2020-09-23 15:53:58 +02:00
self . _error ( " LdapServer - Error renaming %s in %s : %s " % ( dn , new_rdn , e ) , logging . ERROR )
2015-04-01 17:08:26 +02:00
return False
2013-06-07 12:13:03 +02:00
def drop_object ( self , dn ) :
try :
2018-12-12 17:11:23 +01:00
self . logger . debug ( " LdapServer - Delete %s " % dn )
2013-06-07 12:13:03 +02:00
self . con . delete_s ( dn )
return True
except ldap . LDAPError , e :
2020-09-23 15:53:58 +02:00
self . _error ( " LdapServer - Error deleting %s : %s " % ( dn , e ) , logging . ERROR )
2013-06-07 12:13:03 +02:00
return False
def get_dn ( self , obj ) :
return obj [ 0 ] [ 0 ]
2015-04-01 17:08:43 +02:00
def get_attr ( self , obj , attr , all = None , default = None ) :
2018-02-13 11:59:01 +01:00
if attr not in obj :
for k in obj :
if k . lower ( ) == attr . lower ( ) :
attr = k
break
2013-06-07 12:13:03 +02:00
if all is not None :
if attr in obj :
return obj [ attr ]
else :
2015-04-01 17:08:43 +02:00
return default or [ ]
2013-06-07 12:13:03 +02:00
else :
if attr in obj :
return obj [ attr ] [ 0 ]
else :
2015-04-01 17:08:43 +02:00
return default
2015-04-01 17:07:49 +02:00
class LdapServerException ( BaseException ) :
def __init__ ( self , msg ) :
BaseException . __init__ ( self , msg )
2019-06-13 19:15:13 +02:00
#
# Helpers
#
2019-09-30 12:25:58 +02:00
def parse_datetime ( value , to_timezone = None , default_timezone = None , naive = None ) :
2019-07-25 14:05:37 +02:00
"""
Convert LDAP date string to datetime . datetime object
: param value : The LDAP date string to convert
: param to_timezone : If specified , the return datetime will be converted to this
specific timezone ( optional , default : timezone of the LDAP date string )
: param default_timezone : The timezone used if LDAP date string does not specified
the timezone ( optional , default : server local timezone )
2019-09-30 12:25:58 +02:00
: param naive : Use naive datetime : return naive datetime object ( without timezone conversion from LDAP )
2019-07-25 14:05:37 +02:00
"""
2019-07-03 15:54:39 +02:00
assert to_timezone is None or isinstance ( to_timezone , datetime . tzinfo ) or isinstance ( to_timezone , str ) , ' to_timezone must be None, a datetime.tzinfo object or a string (not %s ) ' % type ( to_timezone )
2019-06-16 12:34:10 +02:00
assert default_timezone is None or isinstance ( default_timezone , datetime . tzinfo ) or isinstance ( default_timezone , pytz . tzinfo . DstTzInfo ) or isinstance ( default_timezone , str ) , ' default_timezone parameter must be None, a string, a pytz.tzinfo.DstTzInfo or a datetime.tzinfo object (not %s ) ' % type ( default_timezone )
2019-06-13 19:15:13 +02:00
date = dateutil . parser . parse ( value , dayfirst = False )
if not date . tzinfo :
2019-09-30 12:25:58 +02:00
if naive :
return date
2019-06-13 19:15:13 +02:00
if not default_timezone :
default_timezone = pytz . utc
2020-09-18 14:47:23 +02:00
elif default_timezone == ' local ' :
default_timezone = dateutil . tz . tzlocal ( )
2019-06-13 19:15:13 +02:00
elif isinstance ( default_timezone , str ) :
default_timezone = pytz . timezone ( default_timezone )
if isinstance ( default_timezone , pytz . tzinfo . DstTzInfo ) :
date = default_timezone . localize ( date )
2019-06-13 19:59:42 +02:00
elif isinstance ( default_timezone , datetime . tzinfo ) :
2019-06-13 19:15:13 +02:00
date = date . replace ( tzinfo = default_timezone )
else :
raise Exception ( " It ' s not supposed to happen! " )
2019-09-30 12:25:58 +02:00
elif naive :
return date . replace ( tzinfo = None )
2019-06-13 19:15:13 +02:00
if to_timezone :
2020-09-18 14:47:23 +02:00
if to_timezone == ' local ' :
to_timezone = dateutil . tz . tzlocal ( )
elif isinstance ( to_timezone , str ) :
2019-07-03 15:54:39 +02:00
to_timezone = pytz . timezone ( to_timezone )
2019-06-13 19:59:42 +02:00
return date . astimezone ( to_timezone )
2019-06-13 19:15:13 +02:00
return date
2019-09-30 12:25:58 +02:00
def parse_date ( value , to_timezone = None , default_timezone = None , naive = None ) :
2019-07-25 14:05:37 +02:00
"""
Convert LDAP date string to datetime . date object
: param value : The LDAP date string to convert
: param to_timezone : If specified , the return datetime will be converted to this
specific timezone ( optional , default : timezone of the LDAP date string )
: param default_timezone : The timezone used if LDAP date string does not specified
the timezone ( optional , default : server local timezone )
2019-09-30 12:25:58 +02:00
: param naive : Use naive datetime : do not handle timezone conversion from LDAP
2019-07-25 14:05:37 +02:00
"""
2019-09-30 12:25:58 +02:00
return parse_datetime ( value , to_timezone , default_timezone , naive ) . date ( )
2019-06-13 19:15:13 +02:00
2019-09-30 12:25:58 +02:00
def format_datetime ( value , from_timezone = None , to_timezone = None , naive = None ) :
2019-07-25 14:05:37 +02:00
"""
Convert datetime . datetime object to LDAP date string
: param value : The datetime . datetime object to convert
: param from_timezone : The timezone used if datetime . datetime object is naive ( no tzinfo )
( optional , default : server local timezone )
: param to_timezone : The timezone used in LDAP ( optional , default : UTC )
2019-09-30 12:25:58 +02:00
: param naive : Use naive datetime : datetime store as UTC in LDAP ( without conversion )
2019-07-25 14:05:37 +02:00
"""
2019-06-13 19:15:13 +02:00
assert isinstance ( value , datetime . datetime ) , ' First parameter must be an datetime.datetime object (not %s ) ' % type ( value )
assert from_timezone is None or isinstance ( from_timezone , datetime . tzinfo ) or isinstance ( from_timezone , pytz . tzinfo . DstTzInfo ) or isinstance ( from_timezone , str ) , ' from_timezone parameter must be None, a string, a pytz.tzinfo.DstTzInfo or a datetime.tzinfo object (not %s ) ' % type ( from_timezone )
2019-07-03 15:54:39 +02:00
assert to_timezone is None or isinstance ( to_timezone , datetime . tzinfo ) or isinstance ( to_timezone , str ) , ' to_timezone must be None, a datetime.tzinfo object or a string (not %s ) ' % type ( to_timezone )
2019-09-30 12:25:58 +02:00
if not value . tzinfo and not naive :
2020-09-18 14:47:23 +02:00
if not from_timezone or from_timezone == ' local ' :
2019-06-13 19:15:13 +02:00
from_timezone = dateutil . tz . tzlocal ( )
elif isinstance ( from_timezone , str ) :
from_timezone = pytz . timezone ( from_timezone )
if isinstance ( from_timezone , pytz . tzinfo . DstTzInfo ) :
from_value = from_timezone . localize ( value )
elif isinstance ( from_timezone , datetime . tzinfo ) :
from_value = value . replace ( tzinfo = from_timezone )
else :
raise Exception ( " It ' s not supposed to happen! " )
2019-09-30 12:25:58 +02:00
elif naive :
from_value = value . replace ( tzinfo = pytz . utc )
2019-06-13 19:15:13 +02:00
else :
from_value = copy . deepcopy ( value )
if not to_timezone :
to_timezone = pytz . utc
2020-09-18 14:47:23 +02:00
elif to_timezone == ' local ' :
to_timezone = dateutil . tz . tzlocal ( )
2019-07-03 15:54:39 +02:00
elif isinstance ( to_timezone , str ) :
to_timezone = pytz . timezone ( to_timezone )
2019-06-13 19:15:13 +02:00
to_value = from_value . astimezone ( to_timezone )
2019-06-13 19:59:42 +02:00
datestring = to_value . strftime ( ' % Y % m %d % H % M % S % z ' )
2019-06-13 19:15:13 +02:00
if datestring . endswith ( ' +0000 ' ) :
datestring = datestring . replace ( ' +0000 ' , ' Z ' )
return datestring
2019-09-30 12:25:58 +02:00
def format_date ( value , from_timezone = None , to_timezone = None , naive = None ) :
2019-07-25 14:05:37 +02:00
"""
Convert datetime . date object to LDAP date string
: param value : The datetime . date object to convert
: param from_timezone : The timezone used if datetime . datetime object is naive ( no tzinfo )
( optional , default : server local timezone )
: param to_timezone : The timezone used in LDAP ( optional , default : UTC )
2019-09-30 12:25:58 +02:00
: param naive : Use naive datetime : do not handle timezone conversion before formating
and return datetime as UTC ( because LDAP required a timezone )
2019-07-25 14:05:37 +02:00
"""
2019-06-13 19:15:13 +02:00
assert isinstance ( value , datetime . date ) , ' First parameter must be an datetime.date object (not %s ) ' % type ( value )
2019-09-30 12:25:58 +02:00
return format_datetime ( datetime . datetime . combine ( value , datetime . datetime . min . time ( ) ) , from_timezone , to_timezone , naive )
2019-06-13 19:59:42 +02:00
#
# Tests
#
if __name__ == ' __main__ ' :
2019-09-30 12:25:58 +02:00
now = datetime . datetime . now ( ) . replace ( tzinfo = dateutil . tz . tzlocal ( ) )
2019-06-13 19:59:42 +02:00
print " Now = %s " % now
datestring_now = format_datetime ( now )
print " format_datetime : %s " % datestring_now
print " format_datetime (from_timezone=utc) : %s " % format_datetime ( now . replace ( tzinfo = None ) , from_timezone = pytz . utc )
print " format_datetime (from_timezone=local) : %s " % format_datetime ( now . replace ( tzinfo = None ) , from_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " format_datetime (from_timezone= ' local ' ) : %s " % format_datetime ( now . replace ( tzinfo = None ) , from_timezone = ' local ' )
2019-06-13 19:59:42 +02:00
print " format_datetime (from_timezone=Paris) : %s " % format_datetime ( now . replace ( tzinfo = None ) , from_timezone = ' Europe/Paris ' )
print " format_datetime (to_timezone=utc) : %s " % format_datetime ( now , to_timezone = pytz . utc )
print " format_datetime (to_timezone=local) : %s " % format_datetime ( now , to_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " format_datetime (to_timezone= ' local ' ) : %s " % format_datetime ( now , to_timezone = ' local ' )
2019-07-03 15:54:39 +02:00
print " format_datetime (to_timezone=Tokyo) : %s " % format_datetime ( now , to_timezone = ' Asia/Tokyo ' )
2019-09-30 12:25:58 +02:00
print " format_datetime (naive=True) : %s " % format_datetime ( now , naive = True )
2019-06-13 19:59:42 +02:00
print " format_date : %s " % format_date ( now )
print " format_date (from_timezone=utc) : %s " % format_date ( now . replace ( tzinfo = None ) , from_timezone = pytz . utc )
print " format_date (from_timezone=local) : %s " % format_date ( now . replace ( tzinfo = None ) , from_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " format_date (from_timezone= ' local ' ) : %s " % format_date ( now . replace ( tzinfo = None ) , from_timezone = ' local ' )
2019-06-13 19:59:42 +02:00
print " format_date (from_timezone=Paris) : %s " % format_date ( now . replace ( tzinfo = None ) , from_timezone = ' Europe/Paris ' )
print " format_date (to_timezone=utc) : %s " % format_date ( now , to_timezone = pytz . utc )
print " format_date (to_timezone=local) : %s " % format_date ( now , to_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " format_date (to_timezone= ' local ' ) : %s " % format_date ( now , to_timezone = ' local ' )
2019-07-03 15:54:39 +02:00
print " format_date (to_timezone=Tokyo) : %s " % format_date ( now , to_timezone = ' Asia/Tokyo ' )
2019-09-30 12:25:58 +02:00
print " format_date (naive=True) : %s " % format_date ( now , naive = True )
2019-06-13 19:59:42 +02:00
print " parse_datetime : %s " % parse_datetime ( datestring_now )
print " parse_datetime (default_timezone=utc) : %s " % parse_datetime ( datestring_now [ 0 : - 1 ] , default_timezone = pytz . utc )
print " parse_datetime (default_timezone=local) : %s " % parse_datetime ( datestring_now [ 0 : - 1 ] , default_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " parse_datetime (default_timezone= ' local ' ) : %s " % parse_datetime ( datestring_now [ 0 : - 1 ] , default_timezone = ' local ' )
2019-06-13 19:59:42 +02:00
print " parse_datetime (default_timezone=Paris) : %s " % parse_datetime ( datestring_now [ 0 : - 1 ] , default_timezone = ' Europe/Paris ' )
print " parse_datetime (to_timezone=utc) : %s " % parse_datetime ( datestring_now , to_timezone = pytz . utc )
print " parse_datetime (to_timezone=local) : %s " % parse_datetime ( datestring_now , to_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " parse_datetime (to_timezone= ' local ' ) : %s " % parse_datetime ( datestring_now , to_timezone = ' local ' )
2019-07-03 15:54:39 +02:00
print " parse_datetime (to_timezone=Tokyo) : %s " % parse_datetime ( datestring_now , to_timezone = ' Asia/Tokyo ' )
2019-09-30 12:25:58 +02:00
print " parse_datetime (naive=True) : %s " % parse_datetime ( datestring_now , naive = True )
2019-06-13 19:59:42 +02:00
print " parse_date : %s " % parse_date ( datestring_now )
print " parse_date (default_timezone=utc) : %s " % parse_date ( datestring_now [ 0 : - 1 ] , default_timezone = pytz . utc )
print " parse_date (default_timezone=local) : %s " % parse_date ( datestring_now [ 0 : - 1 ] , default_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " parse_date (default_timezone= ' local ' ) : %s " % parse_date ( datestring_now [ 0 : - 1 ] , default_timezone = ' local ' )
2019-06-13 19:59:42 +02:00
print " parse_date (default_timezone=Paris) : %s " % parse_date ( datestring_now [ 0 : - 1 ] , default_timezone = ' Europe/Paris ' )
print " parse_date (to_timezone=utc) : %s " % parse_date ( datestring_now , to_timezone = pytz . utc )
print " parse_date (to_timezone=local) : %s " % parse_date ( datestring_now , to_timezone = dateutil . tz . tzlocal ( ) )
2020-09-18 14:47:23 +02:00
print " parse_date (to_timezone= ' local ' ) : %s " % parse_date ( datestring_now , to_timezone = ' local ' )
2019-07-03 15:54:39 +02:00
print " parse_date (to_timezone=Tokyo) : %s " % parse_date ( datestring_now , to_timezone = ' Asia/Tokyo ' )
2019-09-30 12:25:58 +02:00
print " parse_date (naive=True) : %s " % parse_date ( datestring_now , naive = True )