-
Notifications
You must be signed in to change notification settings - Fork 0
/
ldap2json.py
246 lines (189 loc) · 6.59 KB
/
ldap2json.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#!/usr/bin/python
'''ldap2json acts as a proxy between HTTP GET requests and an LDAP
directory. Results are returned to the caller using JSON.'''
import os
import sys
import argparse
import ldap
import configobj
import pprint
import urllib
import json
import memcache
import logging
import itertools
import time
from bottle import route,run,request,response,HTTPError
directory = None
cache = None
config = None
class LDAPDirectory (object):
'''A simple wrapper for LDAP connections that exposes a simplified
search interface. At the moment this class only supports anonymous
binds.'''
def __init__ (self, uris,
basedn='',
scope=ldap.SCOPE_SUBTREE,
debug=False,
maxwait=120,
):
self.uris = itertools.cycle(uris)
self.maxwait = maxwait
self.basedn = basedn
self.scope = scope
self.debug = debug
self.connect()
def connect(self):
uri = self.uris.next()
logging.info('Connecting to %s' % uri)
self.dir = ldap.initialize(uri)
self.dir.set_option(ldap.OPT_REFERRALS,0)
self.dir.simple_bind_s('_username_', '_password_')
def search(self, **kwargs):
'''Turns kwargs into an LDAP search filter, executes the search,
and returns the results. The keys in kwargs are ANDed together;
only results meeting *all* criteria will be returned.
If the connection to the LDAP server has been lost, search will try
to reconnect with exponential backoff. The wait time between
reconnection attempts will grow no large than self.maxwait.'''
if not kwargs:
kwargs = { 'objectclass': '*' }
filter = self.build_filter(**kwargs)
tries = 0
while True:
tries += 1
try:
res = self.dir.search_s(
self.basedn,
self.scope,
filterstr=filter)
#modify script to strip out DN of returned object in results
reslist = list(res[0])
reslist.pop(0)
res[0] = tuple(reslist)
#print res[0]
return res
except ldap.SERVER_DOWN:
interval = max(1, min(self.maxwait, (tries-1)*2))
logging.error('Lost connection to LDAP server: '
'reconnecting in %d seconds.' % interval)
time.sleep(interval)
self.connect()
def build_filter(self, **kwargs):
'''Transform a dictionary into an LDAP search filter.'''
filter = []
for k,v in sorted(kwargs.items(), key=lambda x: x[0]):
filter.append('(%s=%s)' % (k,v))
if len(filter) > 1:
return '(&%s)' % ''.join(filter)
else:
return filter[0]
class Cache (object):
'''This is a very simple wrapper over memcache.Client that
lets us specify a default lifetime for cache objects.'''
def __init__ (self, servers, lifetime=600):
self.lifetime = lifetime
self.cache = memcache.Client(servers)
def set(self, k, v):
self.cache.set(k, v, time=self.lifetime)
def get(self, k):
return self.cache.get(k)
@route('/ldap')
def ldapsearch():
'''This method is where web clients interact with ldap2json. Any
request parameters are turned into an LDAP filter, and results are JSON
encoded and returned to the caller.'''
global directory
global cache
global config
callback = None
# This supports JSONP requests, which require that the JSON
# data be wrapped in a function call specified by the
# callback parameter.
if 'callback' in request.GET:
callback = request.GET['callback']
del request.GET['callback']
# jquery adds this to JSONP requests to prevent caching.
if '_' in request.GET:
del request.GET['_']
key = urllib.quote('/ldap/%s/%s' % (
directory.basedn,
request.urlparts.query,
))
res = cache.get(key)
if res is None:
res = directory.search(**request.GET)
cache.set(key, res)
if not res:
raise HTTPError(404)
response.content_type = 'application/json'
text = json.dumps(res, indent=2, encoding='latin1')
# wrap JSON data in function call for JSON responses.
if callback:
text = '%s(%s)' % (callback, text)
return text
def parse_args():
p = argparse.ArgumentParser()
p.add_argument('-d', '--debug', action='store_true',
default=None)
p.add_argument('-f', '--config',
default='ldap2json.conf')
return p.parse_args()
def init_memcache():
global config
global cache
# Extract server list from config file.
servers = config.get('memcache', {}).get(
'servers', '127.0.0.1:11211')
lifetime = config.get('memcache', {}).get('lifetime', 600)
# Make sure we have a Python list of servers.
if isinstance(servers, (str, unicode)):
servers = [servers]
# Make sure we have an integer.
lifetime = int(lifetime)
assert lifetime > 0
assert isinstance(servers, list)
if config.get('debug'):
print >>sys.stderr, 'using memcache servers: %s' % (
servers)
cache = Cache(servers, lifetime=lifetime)
def init_directory():
global directory
global config
uris = config.get('ldap', {}).get( 'uris', ['ldap://x.x.x.x'])
basedn = config.get('ldap', {}).get( 'basedn', '')
# Make sure we have a list of uris.
if isinstance(uris, (str, unicode)):
uris = [uris]
directory = LDAPDirectory(
uris,
basedn=basedn,
debug=config.get('debug'),
)
def init_logging():
logging.basicConfig(level=logging.INFO,
datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s %(name)s [%(levelname)s]: %(message)s',
)
def main():
global directory
global cache
global config
opts = parse_args()
config = configobj.ConfigObj(opts.config)
# Only override config file "debug" setting if --debug
# was explicitly passed on the command line.
if opts.debug is not None:
config['debug'] = opts.debug
if config.get('debug'):
print >>sys.stderr, 'CONFIG:', pprint.pformat(dict(config))
init_logging()
init_memcache()
init_directory()
run(
host=config.get('host', '127.0.0.1'),
port=config.get('port', 8080),
reloader=config.get('debug', False),
)
if __name__ == '__main__':
main()