3web2ldap.app.entry - schema-aware Entry classes
5web2ldap - a web-based LDAP Client,
6see https://www.web2ldap.de for details
8(C) 1998-2022 by Michael Stroeder <michael@stroeder.com>
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
15from collections
import UserDict
18import ldap0.schema.models
19from ldap0.cidict
import CIDict
20from ldap0.schema.models
import (
25from ldap0.schema.subentry
import SubSchema
26from ldap0.dn
import DNObj
27from ldap0.ldif
import LDIFWriter
31from ..log
import logger
32from ..msbase
import GrabKeys
34from .tmpl
import get_variant_filename
35from .gui
import HIDDEN_FIELD
39 object_class_categories,
41from .schema.viewer
import schema_anchor
42from .schema.syntaxes
import (
49INPUT_FORM_LDIF_TMPL =
"""
51 <legend>Raw LDIF data</legend>
52 <textarea name="in_ldif" rows=
"50" cols=
"80" wrap=
"off">{value_ldif}</textarea>
57 <li>Lines containing
"dn:" will be ignored</li>
58 <li>Only the first entry (until first empty line) will be accepted</li>
59 <li>Maximum length
is set to {value_ldifmaxbytes} bytes</li>
60 <li>Allowed URL schemes: {text_ldifurlschemes}</li>
66class DisplayEntry(UserDict):
68 def __init__(self, app, dn, schema, entry, sep_attr, links):
69 assert isinstance(dn, str), TypeError(
"Argument 'dn' must be str, was %r" % (dn))
70 assert isinstance(schema, SubSchema), \
71 TypeError(
'Expected schema to be instance of SubSchema, was %r' % (schema))
75 if isinstance(entry, dict):
76 self.
entry = ldap0.schema.models.Entry(schema, dn, entry)
77 elif isinstance(entry, ldap0.schema.models.Entry):
81 'Invalid type of argument entry, was %s.%s %r' % (
82 entry.__class__.__module__,
83 entry.__class__.__name__,
98 syntax_se = syntax_registry.get_syntax(self.
entry._s, nameoroid, self.
soc)
99 for i, value
in enumerate(values):
100 attr_instance = syntax_se(
109 attr_value_html = attr_instance.display(i, self.
links)
120 attr_value_html = attr_instance.display(i, self.
links)
122 attr_instance.validate(value)
123 except LDAPSyntaxValueError:
124 attr_value_html =
'<s>%s</s>' % (attr_value_html)
126 result.append(attr_value_html)
128 value_sep = getattr(attr_instance, self.
sep_attr)
129 return value_sep.join(result)
134 return DNObj.from_str(self.
dn).rdn_attrs()
137 read_template_dict = CIDict(self.
_app.cfg_param(cnf_key, {}))
139 all_object_class_oid_set = self.
entry.object_class_oid_set()
141 object_class_oid_set = SchemaElementOIDSet(
142 self.
entry._s, ldap0.schema.models.ObjectClass, []
144 structural_oc = self.
entry.get_structural_oc()
146 object_class_oid_set.add(structural_oc)
148 for ocl
in all_object_class_oid_set:
149 ocl_obj = self.
entry._s.get_obj(ldap0.schema.models.ObjectClass, ocl)
150 if ocl_obj
is None or ocl_obj.kind != 0:
151 object_class_oid_set.add(ocl)
152 template_oc = object_class_oid_set.intersection(read_template_dict.data.keys())
153 return template_oc.names, read_template_dict
165 used_templates = set()
166 displayed_attrs = set()
167 for oc_set
in (structural_oc, abstract_oc, auxiliary_oc):
169 read_template_filename = read_template_dict[ocl]
170 logger.debug(
'Template file name %r defined for %r', read_template_dict[ocl], ocl)
171 if not read_template_filename:
172 logger.warning(
'Ignoring empty template file name for %r', ocl)
175 read_template_filename,
176 self.
_app.form.accept_language,
178 if read_template_filename
in used_templates:
181 'Skipping already processed template file name %r for %r',
182 read_template_dict[ocl],
186 used_templates.add(read_template_filename)
188 with open(read_template_filename,
'rb')
as template_file:
189 template_str = template_file.read().decode(
'utf-8')
190 except IOError
as err:
192 'Error reading template file %r for %r: %s',
193 read_template_dict[ocl],
198 template_attr_oid_set = {
199 self.
entry._s.get_oid(ldap0.schema.models.AttributeType, attr_type_name)
200 for attr_type_name
in GrabKeys(template_str)()
203 display_duplicate_attrs
204 or not displayed_attrs.intersection(template_attr_oid_set)
206 self.
_app.outf.write(template_str % self)
207 displayed_attrs.update(template_attr_oid_set)
208 return displayed_attrs
214 self, app, dn, schema, entry,
216 existing_object_classes=None,
219 assert isinstance(dn, str), TypeError(
"Argument 'dn' must be str, was {!r}".format(dn))
220 DisplayEntry.__init__(self, app, dn, schema, entry,
'field_sep',
False)
224 new_object_classes = set(self.
entry.object_class_oid_set()) - {
225 self.
entry._s.get_oid(ObjectClass, oc_name)
226 for oc_name
in existing_object_classes
or []
228 new_attribute_types = self.
entry._s.attribute_types(
231 ignore_dit_content_rule=self.
_app.ls.relax_rules
233 old_attribute_types = self.
entry._s.attribute_types(
234 existing_object_classes
or [],
236 ignore_dit_content_rule=self.
_app.ls.relax_rules
241 for at_oid
in list(old_attribute_types[0].keys())+list(old_attribute_types[1].keys()):
254 Return HTML input field(s) for the attribute specified by nameoroid.
256 oid = self.entry.name2key(nameoroid)[0]
257 nameoroid_se = self.entry._s.get_obj(AttributeType, nameoroid)
258 syntax_class = syntax_registry.get_syntax(self.entry._s, nameoroid, self.soc)
264 attr_values = attr_values
or [
None]
269 if not syntax_class.editable:
274 for attr_index, attr_value
in enumerate(attr_values):
276 attr_inst = syntax_class(
279 highlight_invalid = attr_index
in invalid_attr_indexes
298 self.
rdn_dict[nameoroid].encode(
'utf-8') == attr_value
302 not self.
_app.ls.relax_rules
and
305 result.append(
'\n'.join((
306 '<span class="InvalidInput">'*highlight_invalid,
307 self.
_app.form.hidden_field_html(
'in_at', nameoroid,
''),
311 self.
_app.form.s2d(attr_inst.form_value(), sp_entity=
' '),
312 self.
_app.form.s2d(attr_inst.form_value(), sp_entity=
' ')
314 '</span>'*highlight_invalid,
321 attr_type_name = str(nameoroid).split(
';')[0]
323 attr_type_name = (nameoroid_se.names
or [nameoroid_se.oid])[0]
325 attr_title = (nameoroid_se.desc
or '')
331 nameoroid.endswith(
';binary')
or
332 oid
in NEEDS_BINARY_TAG
or
333 nameoroid_se.syntax
in NEEDS_BINARY_TAG
335 attr_type_tags.append(
'binary')
336 input_fields = attr_inst.input_fields()
337 for input_field
in input_fields:
338 input_field.name =
'in_av'
339 input_field.charset = self.
_app.form.accept_charset
340 result.append(
'\n'.join([
341 '<span class="InvalidInput">'*highlight_invalid,
344 ';'.join([attr_type_name]+attr_type_tags),
349 input_field.input_html(
351 'inputattr', attr_type_name, str(attr_index)
357 '</span>'*highlight_invalid,
363 return '<span id="in_a_%s">%s</span>' % (
364 self.
_app.form.s2d(nameoroid),
365 '\n<br>\n'.join(result),
372 (
'no_user_mod', [0]),
378 if not self.
_app.ls.relax_rules:
379 attr_type_filter.append((
'obsolete', [0]))
382 object_class_oids = self.
entry.object_class_oid_set()
384 object_class_oids.remove(
'1.3.6.1.4.1.1466.101.120.111')
388 object_class_oids.remove(
'extensibleObject')
392 required_attrs_dict, allowed_attrs_dict = self.
entry._s.attribute_types(
393 list(object_class_oids),
394 attr_type_filter=attr_type_filter,
396 ignore_dit_content_rule=self.
_app.ls.relax_rules,
402 if '2.5.4.0' not in required_attrs_dict
and '2.5.4.0' not in allowed_attrs_dict:
403 required_attrs_dict[
'2.5.4.0'] = self.
entry._s.get_obj(ObjectClass,
'2.5.4.0')
404 return required_attrs_dict, allowed_attrs_dict
407 self.
_app.outf.write(
408 """<fieldset title="%s">
411 """ % (fieldset_title, fieldset_title, fieldset_title)
413 seen_attr_type_oids = ldap0.cidict.CIDict()
414 attr_type_names = ldap0.cidict.CIDict()
415 for atype
in self.
entry.keys():
416 at_oid = self.
entry.name2key(atype)[0]
417 if at_oid
in attr_types_dict:
418 seen_attr_type_oids[at_oid] =
None
419 attr_type_names[atype] =
None
420 for at_oid, at_se
in attr_types_dict.items():
423 at_oid
not in seen_attr_type_oids
and
426 attr_type_names[(at_se.names
or (at_se.oid,))[0]] =
None
427 attr_types = list(attr_type_names.keys())
428 attr_types.sort(key=str.lower)
429 for attr_type
in attr_types:
430 attr_type_name =
schema_anchor(self.
_app, attr_type, AttributeType, link_text=
'»')
431 attr_value_field_html = self[attr_type]
432 self.
_app.outf.write(
433 '<tr>\n<td class="InputAttrType">\n%s\n</td>\n<td>\n%s\n</td>\n</tr>\n' % (
435 attr_value_field_html,
438 self.
_app.outf.write(
'</table>\n</fieldset>\n')
443 for attr_dict, fieldset_title
in attrs_dict_list:
450 displayed_attrs = DisplayEntry.template_output(
451 self, cnf_key, display_duplicate_attrs=display_duplicate_attrs
454 for attr_type, attr_values
in self.
entry.items():
455 at_oid = self.
entry.name2key(attr_type)[0]
456 syntax_class = syntax_registry.get_syntax(self.
entry._s, attr_type, self.
soc)
457 if syntax_class.editable
and \
459 not at_oid
in displayed_attrs:
460 for attr_value
in attr_values:
461 attr_inst = syntax_class(
464 self.
_app.outf.write(self.
_app.form.hidden_field_html(
'in_at', attr_type,
''))
467 attr_value_html = self.
_app.form.s2d(attr_inst.form_value(), sp_entity=
' ')
468 except UnicodeDecodeError:
472 self.
_app.outf.write(HIDDEN_FIELD % (
473 'in_av', attr_value_html,
''
476 return displayed_attrs
480 ldif_writer = LDIFWriter(bio)
482 for attr_type
in self.
entry.keys():
485 ldap_entry[attr_type.encode(
'ascii')] = [
487 for attr_value
in attr_values
490 ldif_writer.unparse(self.
dn.encode(self.
_app.ls.charset), ldap_entry)
491 self.
_app.outf.write(
492 INPUT_FORM_LDIF_TMPL.format(
493 value_ldif=self.
_app.form.s2d(
494 bio.getvalue().decode(
'utf-8'),
498 value_ldifmaxbytes=web2ldapcnf.ldif_maxbytes,
499 text_ldifurlschemes=
', '.join(web2ldapcnf.ldif_url_schemes)
def get_html_templates(self, cnf_key)
def template_output(self, cnf_key, display_duplicate_attrs=True)
def __init__(self, app, dn, schema, entry, sep_attr, links)
def __getitem__(self, nameoroid)
def attribute_types(self)
def table_input(self, attrs_dict_list)
def _reset_input_counters(self)
def template_output(self, cnf_key, display_duplicate_attrs=True)
def fieldset_table(self, attr_types_dict, fieldset_title)
def __getitem__(self, nameoroid)
def __init__(self, app, dn, schema, entry, readonly_attr_oids, existing_object_classes=None, invalid_attrs=None)
def no_userapp_attr(schema, attr_type_name, relax_rules=False)
def object_class_categories(sub_schema, object_classes)
def schema_anchor(app, se_nameoroid, se_class, name_template='{name}\n{anchor}', link_text=None)
def get_variant_filename(pathname, variantlist)