1# -*- coding: ascii -*- 2""" 3web2ldap.app.dit: do a tree search and display to the user 4 5web2ldap - a web-based LDAP Client, 6see https://www.web2ldap.de for details 7 8(c) 1998-2021 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 15import ldap0 16from ldap0.dn import DNObj 17 18from ..ldaputil import has_subordinates 19from .gui import dn_anchor_hash, main_menu 20 21 22# All attributes to be read for nodes 23DIT_ATTR_LIST = [ 24 'objectClass', 25 'structuralObjectClass', 26 'displayName', 27 'description', 28 'hasSubordinates', 29 'subordinateCount', 30 'numSubordinates', 31 # Siemens DirX 32 'numAllSubordinates', 33 # Critical Path Directory Server 34 'countImmSubordinates', 35 'countTotSubordinates', 36 # MS Active Directory 37 'msDS-Approx-Immed-Subordinates', 38] 39 40 41def dit_html(app, anchor_dn, dit_dict, entry_dict, max_levels): 42 """ 43 Outputs HTML representation of a directory information tree (DIT) 44 """ 45 46 assert isinstance(anchor_dn, DNObj), ValueError( 47 'Expected anchor_dn to be DNObj, got %r' % (anchor_dn,), 48 ) 49 50 # Start node's HTML 51 res = ['<dl>'] 52 53 for dn, ddat in dit_dict.items(): 54 55 assert isinstance(dn, DNObj), ValueError( 56 'Expected dn to be DNObj, got %r' % (dn,), 57 ) 58 59 try: 60 size_limit = ddat['_sizelimit_'] 61 except KeyError: 62 size_limit = False 63 else: 64 del ddat['_sizelimit_'] 65 66 # Generate anchor for this node 67 if dn: 68 rdn = dn.rdn() 69 else: 70 rdn = 'Root DSE' 71 72 try: 73 node_entry = entry_dict[dn] 74 except KeyError: 75 # Try to read the missing entry 76 try: 77 ldap_res = app.ls.l.read_s(str(dn), attrlist=DIT_ATTR_LIST) 78 except ldap0.LDAPError: 79 node_entry = {} 80 else: 81 node_entry = {} if ldap_res is None else ldap_res.entry_s 82 83 if size_limit: 84 partial_str = '<strong>...</strong>' 85 else: 86 partial_str = '' 87 88 try: 89 display_name_list = [app.form.s2d(node_entry['displayName'][0]), partial_str] 90 except KeyError: 91 display_name_list = [app.form.s2d(str(rdn)), partial_str] 92 display_name = ''.join(display_name_list) 93 94 title_msg = '\r\n'.join( 95 (str(dn) or 'Root DSE', node_entry.get('structuralObjectClass', [''])[0]) + \ 96 tuple(node_entry.get('description', [])) 97 ) 98 99 dn_anchor_id = dn_anchor_hash(dn) 100 101 res.append('<dt id="%s">' % (app.form.s2d(dn_anchor_id))) 102 if has_subordinates(node_entry, default=True): 103 if dn == anchor_dn: 104 link_text = '‹‹' 105 next_dn = dn.parent() 106 else: 107 link_text = '››' 108 next_dn = dn 109 # Only display link if there are subordinate entries expected or unknown 110 res.append( 111 app.anchor( 112 'dit', link_text, 113 [('dn', str(next_dn))], 114 title='Browse from %s' % (str(next_dn),), 115 anchor_id=dn_anchor_id, 116 ) 117 ) 118 else: 119 # FIX ME! Better solution in pure CSS? 120 res.append(' ') 121 res.append('<span title="%s">%s</span>' % ( 122 app.form.s2d(title_msg), 123 display_name 124 )) 125 res.append( 126 app.anchor( 127 'read', '›', 128 [('dn', str(dn))], 129 title='Read entry', 130 ) 131 ) 132 res.append('</dt>') 133 134 # Subordinate nodes' HTML 135 res.append('<dd>') 136 if max_levels and ddat: 137 res.extend(dit_html(app, anchor_dn, ddat, entry_dict, max_levels-1)) 138 res.append('</dd>') 139 140 # Finish node's HTML 141 res.append('</dl>') 142 143 return res # dit_html() 144 145 146def w2l_dit(app): 147 148 dit_dict = {} 149 entry_dict = {} 150 151 root_dit_dict = dit_dict 152 153 dn_levels = len(app.dn_obj) 154 dit_max_levels = app.cfg_param('dit_max_levels', 10) 155 cut_off_levels = max(0, dn_levels-dit_max_levels) 156 157 for i in range(1, dn_levels-cut_off_levels+1): 158 search_base = app.dn_obj.slice(dn_levels-cut_off_levels-i, None) 159 dit_dict[search_base] = {} 160 try: 161 msg_id = app.ls.l.search( 162 str(search_base), 163 ldap0.SCOPE_ONELEVEL, 164 '(objectClass=*)', 165 attrlist=DIT_ATTR_LIST, 166 timeout=app.cfg_param('dit_search_timelimit', 10), 167 sizelimit=app.cfg_param('dit_search_sizelimit', 50), 168 ) 169 for ldap_result in app.ls.l.results(msg_id): 170 # FIX ME! Search continuations are ignored for now 171 if ldap_result.rtype == ldap0.RES_SEARCH_REFERENCE: 172 continue 173 for res in ldap_result.rdata: 174 entry_dict[res.dn_o] = res.entry_s 175 dit_dict[search_base][res.dn_o] = {} 176 except ( 177 ldap0.TIMEOUT, 178 ldap0.SIZELIMIT_EXCEEDED, 179 ldap0.TIMELIMIT_EXCEEDED, 180 ldap0.ADMINLIMIT_EXCEEDED, 181 ldap0.NO_SUCH_OBJECT, 182 ldap0.INSUFFICIENT_ACCESS, 183 ldap0.PARTIAL_RESULTS, 184 ldap0.REFERRAL, 185 ): 186 dit_dict[search_base]['_sizelimit_'] = True 187 else: 188 dit_dict[search_base]['_sizelimit_'] = False 189 dit_dict = dit_dict[search_base] 190 191 if root_dit_dict: 192 outf_lines = dit_html( 193 app, 194 app.dn_obj, 195 root_dit_dict, 196 entry_dict, 197 dit_max_levels, 198 ) 199 else: 200 if app.dn: 201 outf_lines = ['No results.'] 202 else: 203 outf_lines = ['<p>No results for root search.</p>'] 204 for naming_context in app.ls.namingContexts: 205 outf_lines.append( 206 '<p>%s %s</p>' % ( 207 app.anchor( 208 'dit', '››', 209 (('dn', str(naming_context)),), 210 title='Display tree beneath %s' % (naming_context,), 211 ), 212 app.form.s2d(str(naming_context)), 213 ) 214 ) 215 216 app.simple_message( 217 'Tree view', 218 """ 219 <h1>Directory Information Tree</h1> 220 <div id="DIT">%s</div> 221 """ % ('\n'.join(outf_lines)), 222 main_menu_list=main_menu(app), 223 context_menu_list=[] 224 ) 225 226 # end of w2l_dit() 227