web2ldap  1.7.7
About: web2ldap is a full-featured web-based LDAPv3 client.
  Fossies Dox: web2ldap-1.7.7.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

read.py
Go to the documentation of this file.
1# -*- coding: ascii -*-
2"""
3web2ldap.app.read: Read single entry and output as HTML or vCard
4
5web2ldap - a web-based LDAP Client,
6see https://www.web2ldap.de for details
7
8(C) 1998-2022 by Michael Stroeder <michael@stroeder.com>
9
10This software is distributed under the terms of the
11Apache License Version 2.0 (Apache-2.0)
12https://www.apache.org/licenses/LICENSE-2.0
13"""
14
15from collections import UserDict
16
17import ldap0.dn
18from ldap0.cidict import CIDict
19from ldap0.schema.models import SchemaElementOIDSet, AttributeType
20
21from .schema import no_humanreadable_attr, no_userapp_attr
22from .schema.syntaxes import syntax_registry
23from .schema.viewer import schema_anchor
24from . import ErrorExit
25from .tmpl import get_variant_filename
26from .gui import (
27 context_menu_single_entry,
28 footer,
29 header,
30 main_menu,
31 top_section,
32)
33from .form import ExportFormatSelect, InclOpAttrsCheckbox
34from .entry import DisplayEntry
35
36
37class VCardEntry(UserDict):
38
39 def __init__(self, app, entry, out_charset='utf-8'):
40 UserDict.__init__(self, entry)
41 self._app = app
42 self._out_charset = out_charset
43
44 def __getitem__(self, nameoroid):
45 if no_humanreadable_attr(self._app.schema, nameoroid):
46 raise KeyError('Not human-readable attribute %r not usable in vCard' % (nameoroid,))
47 return UserDict.__getitem__(self, nameoroid)[0].decode(self._app.ls.charset)
48
49 def generate_vcard(self, template_str):
50 res = []
51 for line in template_str.decode('utf-8').split('\n'):
52 try:
53 res_line = line % self
54 except KeyError:
55 pass
56 else:
57 res.append(res_line.strip())
58 return '\r\n'.join(res)
59
60
61def get_vcard_template(app, object_classes):
62 template_dict = CIDict(app.cfg_param('vcard_template', {}))
63 current_oc_set = {s.lower().decode('ascii') for s in object_classes}
64 template_oc = list(current_oc_set.intersection(template_dict.data.keys()))
65 if not template_oc:
66 return None
67 return get_variant_filename(template_dict[template_oc[0]], app.form.accept_language)
68
69
70def display_attribute_table(app, entry, attrs, comment):
71 """
72 Send a table of attributes to outf
73 """
74 # Determine which attributes are shown
75 show_attrs = [
76 a
77 for a in attrs
78 if a in entry.entry
79 ]
80 if not show_attrs:
81 # There's nothing to display => exit
82 return
83 show_attrs.sort(key=str.lower)
84 # Determine which attributes are shown expanded or collapsed
85 read_expandattr_set = {
86 at.strip().lower()
87 for at in app.form.getInputValue('read_expandattr', [])
88 if at
89 }
90 if '*' in read_expandattr_set:
91 read_tablemaxcount_dict = {}
92 else:
93 read_tablemaxcount_dict = ldap0.cidict.CIDict(
94 app.cfg_param('read_tablemaxcount', {})
95 )
96 for at in read_expandattr_set:
97 try:
98 del read_tablemaxcount_dict[at]
99 except KeyError:
100 pass
101 app.outf.write('<h2>%s</h2>\n<table class="ReadAttrTable">' % (comment))
102 # Set separation of attribute values inactive
103 entry.sep = None
104 for attr_type_name in show_attrs:
105 attr_type_anchor_id = 'readattr_%s' % app.form.s2d(attr_type_name)
106 attr_type_str = schema_anchor(
107 app,
108 attr_type_name,
109 ldap0.schema.models.AttributeType,
110 name_template='<var>{name}</var>\n{anchor}',
111 link_text='&raquo;'
112 )
113 attr_value_disp_list = (
114 entry[attr_type_name] or
115 ['<strong>&lt;Empty attribute value list!&gt;</strong>']
116 )
117 attr_value_count = len(attr_value_disp_list)
118 dt_list = [
119 '<span id="%s">%s</span>\n' % (attr_type_anchor_id, attr_type_str),
120 ]
121 read_tablemaxcount = min(
122 read_tablemaxcount_dict.get(attr_type_name, attr_value_count),
123 attr_value_count,
124 )
125 if attr_value_count > 1:
126 if attr_value_count > read_tablemaxcount:
127 dt_list.append(app.anchor(
128 'read',
129 '(%d of %d values)' % (read_tablemaxcount, attr_value_count),
130 app.form.allInputFields(
131 fields=[
132 ('read_expandattr', attr_type_name),
133 ],
134 ),
135 anchor_id=attr_type_anchor_id
136 ))
137 else:
138 dt_list.append('(%d values)' % (attr_value_count))
139 if no_humanreadable_attr(app.schema, attr_type_name):
140 if not no_userapp_attr(app.schema, attr_type_name):
141 dt_list.append(app.anchor(
142 'delete', 'Delete',
143 [('dn', app.dn), ('delete_attr', attr_type_name)]
144 ))
145 dt_list.append(app.anchor(
146 'read', 'Save to disk',
147 [
148 ('dn', app.dn),
149 ('read_attr', attr_type_name),
150 ('read_attrmimetype', 'application/octet-stream'),
151 ('read_attrindex', '0'),
152 ],
153 ))
154 dt_str = '<br>'.join(dt_list)
155 app.outf.write(
156 '<tr>\n<th rowspan="%d">\n%s\n</th>\n<td>%s</td>\n</tr>\n' % (
157 read_tablemaxcount,
158 dt_str,
159 attr_value_disp_list[0],
160 )
161 )
162 if read_tablemaxcount >= 2:
163 for i in range(1, read_tablemaxcount):
164 app.outf.write(
165 '<tr>\n<td>%s</td>\n</tr>\n' % (
166 attr_value_disp_list[i],
167 )
168 )
169 app.outf.write('</table>\n')
170 return # display_attribute_table()
171
172
173def w2l_read(app):
174
175 read_output = app.form.getInputValue('read_output', ['template'])[0]
176 filterstr = app.form.getInputValue('filterstr', ['(objectClass=*)'])[0]
177
178 read_nocache = int(app.form.getInputValue('read_nocache', ['0'])[0] or '0')
179
180 # Specific attributes requested with form parameter read_attr?
181 wanted_attr_set = SchemaElementOIDSet(
182 app.schema,
183 ldap0.schema.models.AttributeType,
184 app.form.getInputValue('read_attr', app.ldap_url.attrs or []),
185 )
186 wanted_attrs = wanted_attr_set.names
187
188 # Specific attributes requested with form parameter search_attrs?
189 search_attrs = app.form.getInputValue('search_attrs', [''])[0]
190 if search_attrs:
191 wanted_attrs.extend([
192 a.strip() for a in search_attrs.split(',')
193 ])
194
195 # Determine how to get all attributes including the operational attributes
196 if not wanted_attrs:
197 if app.ls.supports_allop_attr:
198 wanted_attrs = ['*', '+']
199 else:
200 wanted_attrs = []
201 # Read the entry's data
202 search_result = app.ls.l.read_s(
203 app.dn,
204 attrlist=wanted_attrs,
205 filterstr=filterstr,
206 cache_ttl=None if read_nocache else -1.0,
207 )
208
209 if not search_result:
210 raise ErrorExit('Empty search result.')
211
212 entry = ldap0.schema.models.Entry(app.schema, app.dn, search_result.entry_as)
213
214 requested_attrs = SchemaElementOIDSet(
215 app.schema,
216 AttributeType,
217 app.cfg_param('requested_attrs', []),
218 )
219 if not wanted_attrs and requested_attrs:
220 try:
221 search_result = app.ls.l.read_s(
222 app.dn,
223 filterstr=filterstr,
224 attrlist=requested_attrs.names,
225 cache_ttl=None if read_nocache else -1.0,
226 )
227 except (
228 ldap0.NO_SUCH_ATTRIBUTE,
229 ldap0.INSUFFICIENT_ACCESS,
230 ):
231 # Catch and ignore complaints of server about not knowing attribute
232 pass
233 else:
234 if search_result:
235 entry.update(search_result.entry_as)
236
237 display_entry = DisplayEntry(app, app.dn, app.schema, entry, 'read_sep', 1)
238
239 if (
240 wanted_attrs
241 and len(wanted_attrs) == 1
242 and not wanted_attrs[0] in {b'*', b'+'}
243 ):
244
245 # Display a single binary attribute either with a registered
246 # viewer or just by sending the data blob with appropriate MIME-type
247 #-------------------------------------------------------------------
248
249 attr_type = wanted_attrs[0]
250
251 if attr_type not in entry:
252 if attr_type+';binary' in entry:
253 attr_type = attr_type+';binary'
254 else:
255 raise ErrorExit(
256 'Attribute <em>%s</em> not in entry.' % (
257 app.form.s2d(attr_type)
258 )
259 )
260
261 # Send a single binary attribute with appropriate MIME-type
262 read_attrindex = int(app.form.getInputValue('read_attrindex', ['0'])[0])
263 syntax_se = syntax_registry.get_syntax(app.schema, attr_type, entry.get_structural_oc())
264
265 # We have to create an LDAPSyntax instance to be able to call its methods
266 attr_instance = syntax_se(app, app.dn, app.schema, attr_type, None, entry)
267 # Send HTTP header with appropriate MIME type
268 header(
269 app,
270 app.form.getInputValue(
271 'read_attrmimetype',
272 [attr_instance.mime_type],
273 )[0],
274 app.form.accept_charset,
275 more_headers=[
276 (
277 'Content-Disposition',
278 'inline; filename=web2ldap-export.%s' % (attr_instance.file_ext,)
279 ),
280 ]
281 )
282 # send attribute value
283 app.outf.write_bytes(entry[attr_type][read_attrindex])
284
285 return # end of single attribute display
286
287 if read_output in {'table', 'template'}:
288
289 # Display the whole entry with all its attributes
291 app,
292 '',
293 main_menu(app),
294 context_menu_list=context_menu_single_entry(
295 app,
296 vcard_link=not get_vcard_template(app, entry.get('objectClass', [])) is None,
297 dds_link=b'dynamicObject' in entry.get('objectClass', []),
298 entry_uuid=(
299 entry['entryUUID'][0].decode(app.ls.charset)
300 if 'entryUUID' in entry
301 else None
302 )
303 )
304 )
305
306 export_field = ExportFormatSelect()
307 export_field.charset = app.form.accept_charset
308
309 # List of already displayed attributes
310 app.outf.write('%s\n' % (
311 app.form_html(
312 'search', 'Export', 'GET',
313 [
314 ('dn', app.dn),
315 ('scope', '0'),
316 ('filterstr', '(objectClass=*)'),
317 ('search_resnumber', '0'),
318 ('search_attrs', ','.join(map(str, wanted_attrs or []))),
319 ],
320 extrastr='\n'.join((
321 export_field.input_html(),
322 'Incl. op. attrs.:',
323 InclOpAttrsCheckbox().input_html(),
324 )),
325 target='web2ldapexport',
326 ),
327 ))
328
329 displayed_attrs = set()
330
331 if read_output == 'template':
332 # Display attributes with HTML templates
333 displayed_attrs.update(display_entry.template_output('read_template'))
334
335 # Display the DN if no templates were used above
336 if not displayed_attrs:
337 if not app.dn:
338 h1_display_name = 'Root DSE'
339 else:
340 h1_display_name = entry.get(
341 'displayName',
342 entry.get('cn', [b''])
343 )[0].decode(app.ls.charset) or str(app.dn_obj.slice(0, 1))
344 app.outf.write(
345 '<h1>{0}</h1>\n<p class="EntryDN">{1}</p>\n'.format(
346 app.form.s2d(h1_display_name),
347 display_entry['entryDN'],
348 )
349 )
350
351
352 # Display (rest of) attributes as table
353 #-----------------------------------------
354
355 required_attrs_dict, allowed_attrs_dict = entry.attribute_types(raise_keyerror=0)
356
357 # Sort the attributes into different lists according to schema their information
358 required_attrs = []
359 allowed_attrs = []
360 collective_attrs = []
361 nomatching_attrs = []
362 for a in entry.keys():
363 at_se = app.schema.get_obj(ldap0.schema.models.AttributeType, a, None)
364 if at_se is None:
365 nomatching_attrs.append(a)
366 continue
367 at_oid = at_se.oid
368 if at_oid in displayed_attrs:
369 continue
370 if at_oid in required_attrs_dict:
371 required_attrs.append(a)
372 elif at_oid in allowed_attrs_dict:
373 allowed_attrs.append(a)
374 else:
375 if at_se.collective:
376 collective_attrs.append(a)
377 else:
378 nomatching_attrs.append(a)
379
380 display_entry.sep_attr = None
381 display_attribute_table(app, display_entry, required_attrs, 'Required Attributes')
382 display_attribute_table(app, display_entry, allowed_attrs, 'Allowed Attributes')
383 display_attribute_table(app, display_entry, collective_attrs, 'Collective Attributes')
384 display_attribute_table(app, display_entry, nomatching_attrs, 'Various Attributes')
385 display_entry.sep_attr = 'read_sep'
386
387 app.outf.write(
388 """%s\n%s\n%s<p>\n%s\n
389 <input type=submit value="Request"> attributes:
390 <input name="search_attrs" value="%s" size="40" maxlength="255">
391 </p></form>
392 """ % (
393 app.begin_form('read', 'GET'),
394 app.form.hidden_field_html('read_nocache', '1', ''),
395 app.form.hidden_field_html('dn', app.dn, ''),
396 app.form.hidden_field_html('read_output', read_output, ''),
397 ','.join([
398 app.form.s2d(at, sp_entity=' ')
399 for at in (
400 wanted_attrs
401 or {False:['*'], True:['*', '+']}[app.ls.supports_allop_attr]
402 )
403 ])
404 )
405 )
406
407 footer(app)
408
409 elif read_output == 'vcard':
410
411 ##############################################################
412 # vCard export
413 ##############################################################
414
415 vcard_template_filename = get_vcard_template(app, entry.get('objectClass', []))
416
417 if not vcard_template_filename:
418 raise ErrorExit('No vCard template file found for object class(es) of this entry.')
419
420 # Templates defined => display the entry with the help of a template
421 try:
422 with open(vcard_template_filename, 'rb') as fileobj:
423 template_str = fileobj.read()
424 except IOError:
425 raise ErrorExit('I/O error during reading vCard template file!')
426
427 vcard_filename = 'web2ldap-vcard'
428 for vcard_name_attr in ('displayName', 'cn', 'o'):
429 try:
430 vcard_filename = entry[vcard_name_attr][0].decode(app.ls.charset)
431 except (KeyError, IndexError):
432 pass
433 else:
434 break
435 entry['dn'] = [app.ldap_dn]
436 display_entry = VCardEntry(app, entry)
437 header(
438 app,
439 'text/x-vcard',
440 app.form.accept_charset,
441 more_headers=[
442 (
443 'Content-Disposition',
444 'inline; filename={0}.vcf'.format(vcard_filename)
445 ),
446 ],
447 )
448 app.outf.write(display_entry.generate_vcard(template_str))
def generate_vcard(self, template_str)
Definition: read.py:49
def __init__(self, app, entry, out_charset='utf-8')
Definition: read.py:39
def __getitem__(self, nameoroid)
Definition: read.py:44
def top_section(app, title, main_menu_list, context_menu_list=None, main_div_id='Message')
Definition: gui.py:352
def header(app, content_type, charset, more_headers=None)
Definition: gui.py:459
def context_menu_single_entry(app, vcard_link=0, dds_link=0, entry_uuid=None)
Definition: gui.py:86
def main_menu(app)
Definition: gui.py:234
def footer(app)
Definition: gui.py:477
def get_vcard_template(app, object_classes)
Definition: read.py:61
def display_attribute_table(app, entry, attrs, comment)
Definition: read.py:70
def w2l_read(app)
Definition: read.py:173
def no_humanreadable_attr(schema, attr_type)
Definition: __init__.py:101
def no_userapp_attr(schema, attr_type_name, relax_rules=False)
Definition: __init__.py:81
def schema_anchor(app, se_nameoroid, se_class, name_template='{name}\n{anchor}', link_text=None)
Definition: __init__.py:205
def get_variant_filename(pathname, variantlist)
Definition: tmpl.py:20