1# -*- coding: ascii -*- 2""" 3web2ldap.app.searchform: different search forms 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 16 17import web2ldapcnf 18 19from ..web.forms import Select as SelectField 20from ..log import logger 21 22from . import ErrorExit 23from .gui import attrtype_select_field, search_root_field 24from .gui import footer, main_menu, top_section 25from .tmpl import get_variant_filename 26 27 28SEARCHFORM_MODE_TEXT = { 29 'adv': 'Advanced', 30 'base': 'Basic', 31 'exp': 'Expert', 32} 33 34SEARCH_OPT_CONTAINS = '({at}=*{av}*)' 35SEARCH_OPT_DOESNT_CONTAIN = '(!({at}=*{av}*))' 36SEARCH_OPT_ATTR_EXISTS = '({at}=*)' 37SEARCH_OPT_ATTR_NOT_EXISTS = '(!({at}=*))' 38SEARCH_OPT_IS_EQUAL = '({at}={av})' 39SEARCH_OPT_IS_NOT = '(!({at}={av}))' 40SEARCH_OPT_BEGINS_WITH = '({at}={av}*)' 41SEARCH_OPT_ENDS_WITH = '({at}=*{av})' 42SEARCH_OPT_SOUNDS_LIKE = '({at}~={av})' 43SEARCH_OPT_GE_THAN = '({at}>={av})' 44SEARCH_OPT_LE_THAN = '({at}<={av})' 45SEARCH_OPT_DN_ATTR_IS = '({at}:dn:={av})' 46SEARCH_OPT_DN_SUBORDINATE = '({at}:dnSubordinateMatch:={av})' 47SEARCH_OPT_DN_SUBTREE = '({at}:dnSubtreeMatch:={av})' 48SEARCH_OPT_DN_ONE_LEVEL = '({at}:dnOneLevelMatch:={av})' 49 50SEARCH_SCOPE_STR_BASE = '0' 51SEARCH_SCOPE_STR_ONELEVEL = '1' 52SEARCH_SCOPE_STR_SUBTREE = '2' 53SEARCH_SCOPE_STR_SUBORDINATES = '3' 54 55SEARCH_SCOPE_OPTIONS = [ 56 (str(ldap0.SCOPE_BASE), 'Base'), 57 (str(ldap0.SCOPE_ONELEVEL), 'One level'), 58 (str(ldap0.SCOPE_SUBTREE), 'Sub tree'), 59 (str(ldap0.SCOPE_SUBORDINATE), 'Subordinate'), 60] 61 62SEARCH_OPTIONS = ( 63 (SEARCH_OPT_IS_EQUAL, 'attribute value is'), 64 (SEARCH_OPT_CONTAINS, 'attribute value contains'), 65 (SEARCH_OPT_DOESNT_CONTAIN, 'attribute value does not contain'), 66 (SEARCH_OPT_IS_NOT, 'attribute value is not'), 67 (SEARCH_OPT_BEGINS_WITH, 'attribute value begins with'), 68 (SEARCH_OPT_ENDS_WITH, 'attribute value ends with'), 69 (SEARCH_OPT_SOUNDS_LIKE, 'attribute value sounds like'), 70 (SEARCH_OPT_GE_THAN, 'attribute value greater equal than'), 71 (SEARCH_OPT_LE_THAN, 'attribute value lesser equal than'), 72 (SEARCH_OPT_DN_ATTR_IS, 'DN attribute value is'), 73 (SEARCH_OPT_ATTR_EXISTS, 'entry has attribute'), 74 (SEARCH_OPT_ATTR_NOT_EXISTS, 'entry does not have attribute'), 75 (SEARCH_OPT_DN_SUBORDINATE, 'DN is subordinate of'), 76 (SEARCH_OPT_DN_SUBTREE, 'DN within subtree'), 77 (SEARCH_OPT_DN_ONE_LEVEL, 'DN is direct child of'), 78) 79 80FILTERSTR_FIELDSET_TMPL = """ 81<fieldset> 82 <legend>LDAP filter string</legend> 83 <input name="filterstr" maxlength="%d" size="%d" value="%s"> 84</fieldset> 85""" 86 87 88def search_form_exp(app, filterstr=''): 89 """ 90 Output expert search form 91 """ 92 filterstr = app.form.getInputValue('filterstr', [filterstr])[0] 93 result = FILTERSTR_FIELDSET_TMPL % ( 94 app.form.field['filterstr'].maxLen, 95 app.form.field['filterstr'].size, 96 app.form.s2d(filterstr), 97 ) 98 return result 99 100 101def search_form_base(app, searchform_template_name): 102 """ 103 Output basic search form based on a HTML template configured 104 with host-specific configuration parameter searchform_template 105 """ 106 searchform_template_cfg = app.cfg_param('searchform_template', '') 107 searchform_template = searchform_template_cfg.get(searchform_template_name, None) 108 searchform_template_filename = get_variant_filename( 109 searchform_template, 110 app.form.accept_language, 111 ) 112 with open(searchform_template_filename, 'rb') as fileobj: 113 template_str = fileobj.read().decode('utf-8') 114 return template_str 115 116 117def search_form_adv(app): 118 """advanced search form with select lists""" 119 120 search_submit = app.form.getInputValue('search_submit', [''])[0] 121 122 # Get input values 123 search_attr_list = app.form.getInputValue('search_attr', ['']) 124 search_option_list = app.form.getInputValue('search_option', [None]*len(search_attr_list)) 125 search_mr_list = app.form.getInputValue('search_mr', [None]*len(search_attr_list)) 126 search_string_list = app.form.getInputValue('search_string', ['']*len(search_attr_list)) 127 128 if search_submit.startswith('-'): 129 del_row_num = int(search_submit[1:]) 130 if len(search_attr_list) > 1: 131 del search_option_list[del_row_num] 132 del search_attr_list[del_row_num] 133 del search_mr_list[del_row_num] 134 del search_string_list[del_row_num] 135 elif search_submit.startswith('+'): 136 insert_row_num = int(search_submit[1:]) 137 if len(search_attr_list) < web2ldapcnf.max_searchparams: 138 search_option_list.insert(insert_row_num+1, search_option_list[insert_row_num]) 139 search_attr_list.insert(insert_row_num+1, search_attr_list[insert_row_num]) 140 search_mr_list.insert(insert_row_num+1, search_mr_list[insert_row_num]) 141 search_string_list.insert(insert_row_num+1, '') 142 143 if not len(search_option_list) == len(search_attr_list) == len(search_string_list): 144 raise ErrorExit('Invalid search form data.') 145 146 search_mode = app.form.getInputValue('search_mode', ['(&%s)'])[0] 147 148 search_mode_select = SelectField( 149 'search_mode', 'Search mode', 1, 150 options=[ 151 ('(&%s)', 'all'), 152 ('(|%s)', 'any'), 153 ], 154 default=search_mode 155 ) 156 search_mode_select.charset = app.form.accept_charset 157 158 search_attr_select = attrtype_select_field( 159 app, 160 'search_attr', 161 'Search attribute type', 162 search_attr_list, 163 default_attr_options=app.cfg_param('search_attrs', []) 164 ) 165 166 mr_list = [''] + sorted(app.schema.name2oid[ldap0.schema.models.MatchingRule].keys()) 167 # Create a select field instance for matching rule name 168 search_mr_select = SelectField( 169 'search_mr', 'Matching rule used', 170 web2ldapcnf.max_searchparams, 171 options=mr_list, 172 ) 173 search_mr_select.charset = app.form.accept_charset 174 175 search_fields_html_list = [] 176 177 # Output a row of the search form 178 for i in range(len(search_attr_list)): 179 search_fields_html_list.append('\n'.join(( 180 '<tr>\n<td rowspan="2">', 181 '<button type="submit" name="search_submit" value="+%d">+</button>' % (i), 182 '<button type="submit" name="search_submit" value="-%d">-</button>' % (i), 183 '</td>\n<td>', 184 search_attr_select.input_html(default=search_attr_list[i]), 185 search_mr_select.input_html(default=search_mr_list[i]), 186 app.form.field['search_option'].input_html(default=search_option_list[i]), 187 '</td></tr>\n<tr><td>', 188 app.form.field['search_string'].input_html(default=search_string_list[i]), 189 '</td></tr>', 190 ))) 191 192 # Eigentliches Suchformular ausgeben 193 result = """ 194 <fieldset> 195 <legend>Search filter parameters</legend> 196 Match %s of the following.<br> 197 <table>%s</table> 198 </fieldset> 199 """ % ( 200 search_mode_select.input_html(), 201 '\n'.join(search_fields_html_list), 202 ) 203 return result 204 205 206def w2l_searchform( 207 app, 208 msg='', 209 filterstr='', 210 scope=ldap0.SCOPE_SUBTREE, 211 search_root=None, 212 searchform_mode=None, 213 ): 214 """Output a search form""" 215 216 if msg: 217 msg_html = '<p class="ErrorMessage">%s</p>' % (msg) 218 else: 219 msg_html = '' 220 221 searchform_mode = searchform_mode or app.form.getInputValue('searchform_mode', ['base'])[0] 222 searchform_template_name = app.form.getInputValue('searchform_template', ['_'])[0] 223 224 search_root = app.form.getInputValue( 225 'search_root', 226 [search_root or str(app.naming_context)], 227 )[0] 228 srf = search_root_field( 229 app, 230 name='search_root', 231 default=search_root, 232 search_root_searchurl=app.cfg_param('searchform_search_root_url', None), 233 ) 234 235 ctx_menu_items = [ 236 app.anchor( 237 'searchform', SEARCHFORM_MODE_TEXT[mode], 238 [ 239 ('dn', app.dn), 240 ('searchform_mode', mode), 241 ('search_root', search_root), 242 ('filterstr', filterstr), 243 ('scope', str(scope)), 244 ], 245 ) 246 for mode in SEARCHFORM_MODE_TEXT 247 if mode != searchform_mode 248 ] 249 250 searchform_template_cfg = app.cfg_param('searchform_template', '') 251 if isinstance(searchform_template_cfg, dict): 252 for sftn in searchform_template_cfg.keys(): 253 if sftn != '_': 254 ctx_menu_items.append(app.anchor( 255 'searchform', app.form.s2d(sftn), 256 [ 257 ('dn', app.dn), 258 ('searchform_mode', 'base'), 259 ('searchform_template', sftn), 260 ('search_root', search_root), 261 ('filterstr', filterstr), 262 ('scope', str(scope)), 263 ], 264 )) 265 266 if searchform_mode == 'base': 267 # base search form with fixed input fields 268 try: 269 inner_searchform_html = search_form_base(app, searchform_template_name) 270 except IOError as err: 271 logger.warning('Error loading search form template: %s', err) 272 msg_html = '\n'.join(( 273 msg_html, 274 '<p class="ErrorMessage">I/O error while loading search form template!</p>' 275 )) 276 inner_searchform_html = search_form_adv(app) 277 searchform_mode = 'adv' 278 elif searchform_mode == 'exp': 279 # expert search form with single filter input field 280 inner_searchform_html = search_form_exp(app, filterstr) 281 elif searchform_mode == 'adv': 282 # base search form with fixed input fields 283 inner_searchform_html = search_form_adv(app) 284 285 searchoptions_template_filename = get_variant_filename( 286 app.cfg_param('searchoptions_template', None), 287 app.form.accept_language 288 ) 289 with open(searchoptions_template_filename, 'r') as template_file: 290 searchoptions_template_str = template_file.read() 291 292 top_section( 293 app, 294 '%s Search Form' % SEARCHFORM_MODE_TEXT[searchform_mode], 295 main_menu(app), 296 context_menu_list=ctx_menu_items, 297 main_div_id='Input' 298 ) 299 300 app.outf.write( 301 """ 302 {msg_html} 303 {form_search_html} 304 <input type="hidden" name="searchform_mode" value="{searchform_mode}"> 305 <input type="hidden" name="searchform_template" value="{searchform_template}"> 306 <input type="hidden" name="search_output" value="{search_output}"> 307 <p> 308 <input type="submit" name="search_submit" value="Search"> 309 <input type="reset" value="Reset"> 310 </p> 311 {inner_searchform_html} 312 {form_dn_html} 313 {searchoptions_template_str} 314 </form> 315 """.format( 316 form_search_html=app.begin_form('search', 'GET'), 317 searchform_mode=app.form.s2d(searchform_mode), 318 searchform_template=app.form.s2d(searchform_template_name), 319 search_output=app.form.getInputValue('search_output', ['table'])[0], 320 msg_html=msg_html, 321 inner_searchform_html=inner_searchform_html, 322 form_dn_html=app.form.hidden_field_html('dn', app.dn, ''), 323 searchoptions_template_str=searchoptions_template_str.format( 324 field_search_root=srf.input_html(), 325 field_search_scope=app.form.field['scope'].input_html( 326 default=app.form.getInputValue('scope', [str(scope)])[0] 327 ), 328 field_search_resnumber=app.form.field['search_resnumber'].input_html( 329 default=app.form.getInputValue( 330 'search_resnumber', 331 [str(app.cfg_param('search_resultsperpage', 10))], 332 )[0] 333 ), 334 field_search_lastmod=app.form.field['search_lastmod'].input_html( 335 default=app.form.getInputValue('search_lastmod', [str(-1)])[0] 336 ), 337 value_search_attrs=app.form.s2d(app.form.getInputValue('search_attrs', [''])[0]), 338 ), 339 ) 340 ) 341 342 footer(app) 343