def _get_json_body(view: "View", rows: Rows) -> str: painted_rows: List[List] = [] header_row = [] for cell in view.row_cells: header_row.append(escaping.strip_tags(cell.export_title())) painted_rows.append(header_row) for row in rows: painted_row: List[Union[str, Dict]] = [] for cell in view.row_cells: joined_row = join_row(row, cell) content = cell.render_for_export(joined_row) if isinstance(content, dict): # Allow painters to return lists and dicts, then json encode them # as such data structures without wrapping them into strings pass else: content = escaping.strip_tags(content.replace("<br>", "\n")) painted_row.append(content) painted_rows.append(painted_row) return json.dumps(painted_rows, indent=True)
def _write_csv(self, csv_separator: str) -> None: rows = self.rows limit = self.limit omit_headers = self.options["omit_headers"] # Apply limit after search / sorting etc. if limit is not None: rows = rows[:limit] resp = [] # If we have no group headers then paint the headers now if not omit_headers and self.rows and not isinstance( self.rows[0], GroupHeader): resp.append( csv_separator.join([ escaping.strip_tags(header.title) or "" for header in self.headers ]) + "\n") for row in rows: if isinstance(row, GroupHeader): continue resp.append( csv_separator.join( [escaping.strip_tags(cell.content) for cell in row.cells])) resp.append("\n") response.set_data("".join(resp))
def _export_audit_log(self, audit: List[AuditLogStore.Entry]) -> ActionResult: response.set_content_type("text/csv") if self._options["display"] == "daily": filename = "wato-auditlog-%s_%s.csv" % ( render.date(time.time()), render.time_of_day(time.time()), ) else: filename = "wato-auditlog-%s_%s_days.csv" % ( render.date(time.time()), self._options["display"][1], ) response.headers[ "Content-Disposition"] = 'attachment; filename="%s"' % filename titles = [ _("Date"), _("Time"), _("Object type"), _("Object"), _("User"), _("Action"), _("Summary"), ] if self._show_details: titles.append(_("Details")) resp = [] resp.append(",".join(titles) + "\n") for entry in audit: columns = [ render.date(int(entry.time)), render.time_of_day(int(entry.time)), entry.object_ref.object_type.name if entry.object_ref else "", entry.object_ref.ident if entry.object_ref else "", entry.user_id, entry.action, '"' + escaping.strip_tags(entry.text).replace('"', "'") + '"', ] if self._show_details: columns.append( '"' + escaping.strip_tags(entry.diff_text).replace('"', "'") + '"') resp.append(",".join(columns) + "\n") response.set_data("".join(resp)) return FinalizeRequest(code=200)
def _serialize_ruleset(ruleset: Ruleset) -> DomainObject: members = {} if ruleset.num_rules() > 0: members["rules"] = constructors.collection_property( name="rules", value=[], base="", ) return constructors.domain_object( domain_type="ruleset", identifier=ruleset.name, title=ruleset.title() or ruleset.name, editable=False, deletable=False, members=members, extensions={ "name": ruleset.name, "title": ruleset.title(), "item_type": ruleset.item_type(), "item_name": ruleset.item_name(), "item_enum": ruleset.item_enum(), "match_type": ruleset.match_type(), "help": strip_tags(ruleset.help()), "number_of_rules": ruleset.num_rules(), }, )
def _sort_rows(rows: TableRows, sort_col: int, sort_reverse: int) -> TableRows: # remove and remind fixed rows, add to separate list fixed_rows = [] for index, row in enumerate(rows[:]): if row.fixed: rows.remove(row) fixed_rows.append((index, row)) # Then use natural sorting to sort the list. Note: due to a # change in the number of columns of a table in different software # versions the cmp-function might fail. This is because the sorting # column is persisted in a user file. So we ignore exceptions during # sorting. This gives the user the chance to change the sorting and # see the table in the first place. try: rows.sort( key=lambda x: utils.key_num_split( escaping.strip_tags(x[0][sort_col][0])), reverse=sort_reverse == 1, ) except IndexError: pass # Now re-add the removed "fixed" rows to the list again if fixed_rows: for index, cells in fixed_rows: rows.insert(index, cells) return rows
def render(self, row: Row, cell: Cell) -> CellSpec: classes = ["perfometer"] if is_stale(row): classes.append("stale") try: title, h = Perfometer(row).render() if title is None and h is None: return "", "" except Exception as e: logger.exception("error rendering performeter") if config.debug: raise return " ".join(classes), _("Exception: %s") % e assert h is not None content = (html.render_div(HTML(h), class_=["content"]) + html.render_div(title, class_=["title"]) + html.render_div("", class_=["glass"])) # pnpgraph_present: -1 means unknown (path not configured), 0: no, 1: yes if display_options.enabled( display_options.X) and row["service_pnpgraph_present"] != 0: url = cmk_graph_url(row, "service") disabled = False else: url = "javascript:void(0)" disabled = True return " ".join(classes), html.render_a( content=content, href=url, title=escaping.strip_tags(title), class_=["disabled" if disabled else None], )
def _render_message( self, msg: Union[HTML, str], msg_type: Literal["message", "warning", "error"] = "message", ) -> HTML: if msg_type == "message": cls = "success" prefix = _("MESSAGE") elif msg_type == "warning": cls = "warning" prefix = _("WARNING") elif msg_type == "error": cls = "error" prefix = _("ERROR") else: raise TypeError(msg_type) if self.output_format == "html": code = HTMLWriter.render_div(msg, class_=cls) if self.mobile: return HTMLWriter.render_center(code) return code return escaping.escape_to_html_permissive( "%s: %s\n" % (prefix, escaping.strip_tags(msg)), escape_links=False)
def _format_for_csv(self, raw_data: ExportCellContent) -> str: # raw_data can also be int, float, dict (labels) if isinstance(raw_data, dict): return ', '.join( ["%s: %s" % (key, value) for key, value in raw_data.items()]) return escaping.strip_tags(raw_data).replace('\n', '').replace('"', '""')
def create_rule(param): """Create rule""" body = param["body"] folder = body["folder"] value = body["value_raw"] rulesets = watolib.FolderRulesets(folder) rulesets.load() try: ruleset = rulesets.get(body["ruleset"]) except KeyError: return problem( status=400, detail=f"Ruleset {body['ruleset']!r} could not be found.", ) try: ruleset.valuespec().validate_value(value, "") except exceptions.MKUserError as exc: if exc.varname is None: title = "A field has a problem" else: field_name = exc.varname.replace("_p_", "") title = f"Problem in (sub-)field {field_name!r}" return problem( status=400, detail=strip_tags(exc.message), title=title, ) rule = watolib.Rule( gen_id(), folder, ruleset, RuleConditions( host_folder=folder, host_tags=body["conditions"].get("host_tag"), host_labels=body["conditions"].get("host_label"), host_name=body["conditions"].get("host_name"), service_description=body["conditions"].get("service_description"), service_labels=body["conditions"].get("service_label"), ), RuleOptions.from_config(body["properties"]), value, ) index = ruleset.append_rule(folder, rule) rulesets.save() # TODO Duplicated code is in pages/rulesets.py:2670- # TODO Move to watolib add_change( "new-rule", _l('Created new rule #%d in ruleset "%s" in folder "%s"') % (index, ruleset.title(), folder.alias_path()), sites=folder.all_site_ids(), diff_text=make_diff_text({}, rule.to_log()), object_ref=rule.object_ref(), ) return serve_json(_serialize_rule(folder, index, rule))
def log_audit(action: str, message: LogMessage, object_ref: Optional[ObjectRef] = None, user_id: Optional[UserId] = None, diff_text: Optional[str] = None) -> None: if config.wato_use_git: if isinstance(message, HTML): message = escaping.strip_tags(message.value) cmk.gui.watolib.git.add_message(message) _log_entry(action, message, object_ref, user_id, diff_text)
def do_site_login(site_id: SiteId, name: UserId, password: str) -> str: sites = SiteManagementFactory().factory().load_sites() site = sites[site_id] if not name: raise MKUserError( "_name", _("Please specify your administrator login on the remote site.")) if not password: raise MKUserError("_passwd", _("Please specify your password.")) # Trying basic auth AND form based auth to ensure the site login works. # Adding _ajaxid makes the web service fail silently with an HTTP code and # not output HTML code for an error screen. url = site["multisiteurl"] + "login.py" post_data = { "_login": "******", "_username": name, "_password": password, "_origtarget": "automation_login.py?_version=%s&_edition_short=%s" % (cmk_version.__version__, cmk_version.edition_short()), "_plain_error": "1", } response = get_url(url, site.get("insecure", False), auth=(name, password), data=post_data).strip() if "<html>" in response.lower(): message = _("Authentication to web service failed.<br>Message:<br>%s" ) % escaping.strip_tags(escaping.strip_scripts(response)) if config.debug: message += "<br>" + _("Automation URL:") + " <tt>%s</tt><br>" % url raise MKAutomationException(message) if not response: raise MKAutomationException(_("Empty response from web service")) try: eval_response = ast.literal_eval(response) except SyntaxError: raise MKAutomationException(response) if isinstance(eval_response, dict): if cmk_version.is_managed_edition( ) and eval_response["edition_short"] != "cme": raise MKUserError( None, _("The Check_MK Managed Services Edition can only " "be connected with other sites using the CME."), ) return eval_response["login_secret"] return eval_response
def _show_crash_dump_message( crash: "GUICrashReport", plain_text: bool, fail_silently: bool, show_crash_link: Optional[bool] ) -> None: """Create a crash dump from a GUI exception and display a message to the user""" if show_crash_link is None: show_crash_link = user.may("general.see_crash_reports") title = _("Internal error") message = "%s: %s<br>\n<br>\n" % (title, crash.crash_info["exc_value"]) # Do not reveal crash context information to unauthenticated users or not permitted # users to prevent disclosure of internal information if not show_crash_link: message += _( "An internal error occurred while processing your request. " "You can report this issue to your Checkmk administrator. " "Detailed information can be found on the crash report page " "or in <tt>var/log/web.log</tt>." ) else: crash_url = makeuri( request, [ ("site", omd_site()), ("crash_id", crash.ident_to_text()), ], filename="crash.py", ) message += ( _( "An internal error occured while processing your request. " "You can report this issue to the Checkmk team to help " 'fixing this issue. Please open the <a href="%s">crash report page</a> ' "and use the form for reporting the problem." ) % crash_url ) if plain_text: response.set_content_type("text/plain") response.set_data("%s\n" % escaping.strip_tags(message)) return if fail_silently: return html.header(title, Breadcrumb()) html.show_error(message) html.footer()
def section( title: Union[None, HTML, str] = None, checkbox: Union[None, HTML, str, Tuple[str, bool, str]] = None, section_id: Optional[str] = None, simple: bool = False, hide: bool = False, legend: bool = True, css: Optional[str] = None, is_show_more: bool = False, is_changed: bool = False, is_required: bool = False, ) -> None: global g_section_open section_close() html.open_tr( id_=section_id, class_=[ css, "show_more_mode" if is_show_more and not is_changed else "basic" ], style="display:none;" if hide else None, ) if legend: html.open_td(class_=["legend", "simple" if simple else None]) if title: html.open_div( class_=["title", "withcheckbox" if checkbox else None], title=escaping.strip_tags(title), ) html.write_text(title) html.span("." * 200, class_=["dots", "required" if is_required else None]) html.close_div() if checkbox: html.open_div(class_="checkbox") if isinstance(checkbox, (str, HTML)): html.write_text(checkbox) else: name, active, attrname = checkbox html.checkbox(name, active, onclick="cmk.wato.toggle_attribute(this, '%s')" % attrname) html.close_div() html.close_td() html.open_td(class_=["content", "simple" if simple else None]) g_section_open = True
def _export_python(view: "View", rows: Rows) -> None: resp = [] resp.append("[\n") resp.append(repr([cell.export_title() for cell in view.row_cells])) resp.append(",\n") for row in rows: resp.append("[") for cell in view.row_cells: joined_row = join_row(row, cell) content = cell.render_for_export(joined_row) # The aggr_treestate painters are returning a dictionary data structure (see # paint_aggregated_tree_state()) in case the output_format is not HTML. Only # remove the HTML tags from the top level strings produced by painters. if isinstance(content, str): content = escaping.strip_tags(content) resp.append(repr(content)) resp.append(",") resp.append("],") resp.append("\n]\n") response.set_data("".join(resp))
def collect_attributes( object_type: ObjectType, context: ObjectContext, ) -> List[Attr]: """Collect all attributes for a specific use case Use cases can be host or folder creation or updating. Args: object_type: Either 'host', 'folder' or 'cluster' context: Either 'create' or 'update' Returns: A list of attribute describing named-tuples. Examples: >>> attrs = collect_attributes('host', 'create') >>> assert len(attrs) > 10, len(attrs) >>> attrs = collect_attributes('host', 'update') >>> assert len(attrs) > 10, len(attrs) >>> attrs = collect_attributes('cluster', 'create') >>> assert len(attrs) > 10, len(attrs) >>> attrs = collect_attributes('cluster', 'update') >>> assert len(attrs) > 10, len(attrs) >>> attrs = collect_attributes('folder', 'create') >>> assert len(attrs) > 10, len(attrs) >>> attrs = collect_attributes('folder', 'update') >>> assert len(attrs) > 10 To check the content of the list, uncomment this one. # >>> import pprint # >>> pprint.pprint(attrs) """ something = TypeVar("something") def _ensure(optional: Optional[something]) -> something: if optional is None: raise ValueError return optional T = typing.TypeVar("T") def maybe_call(func: Optional[Callable[[], T]]) -> Optional[T]: if func is None: return None return func() # NOTE: # We want to get all the topics, so we don't miss any attributes. We filter them later. # new=True may also be new=False, it doesn't matter in this context. result = [] for topic_id, topic_title in watolib.get_sorted_host_attribute_topics( "always", new=True): for attr in watolib.get_sorted_host_attributes_by_topic(topic_id): if object_type == "folder" and not attr.show_in_folder(): continue if context in ["create", "update"] and not attr.openapi_editable(): continue help_text: str = strip_tags(attr.help()) or "" # TODO: what to do with attr.depends_on_tags()? attr_entry = Attr( name=attr.name(), description=help_text, section=topic_title, mandatory=attr.is_mandatory(), field=maybe_call(getattr(attr, "openapi_field", None)), ) result.append(attr_entry) tag_config = load_tag_config() tag_config += BuiltinTagConfig() def _format(tag_id: Optional[str]) -> str: if tag_id is None: return "`null`" return f'`"{tag_id}"`' tag_group: TagGroup for tag_group in tag_config.tag_groups: description: List[str] = [] if tag_group.help: description.append(tag_group.help) if tag_group.tags: description.append("Choices:") for tag in tag_group.tags: description.append(f" * {_format(tag.id)}: {tag.title}") result.append( Attr( name=_ensure(f"tag_{tag_group.id}"), section=tag_group.topic or "No topic", mandatory=False, description="\n\n".join(description), enum=[tag.id for tag in tag_group.tags], field=None, )) return result
def test_strip_tags(inp, out): assert escaping.strip_tags(inp) == out
def _show_configuration_variables(self) -> None: search = self._search at_least_one_painted = False html.open_div(class_="globalvars") for group, config_variables in self.iter_all_configuration_variables(): header_is_painted = False # needed for omitting empty groups for config_variable in config_variables: varname = config_variable.ident() valuespec = config_variable.valuespec() if self._show_only_modified and varname not in self._current_settings: continue help_text = valuespec.help() or "" title_text = valuespec.title() or "" if (search and search not in group.title().lower() and search not in config_variable.domain().ident().lower() and search not in varname and search not in help_text.lower() and search not in title_text.lower()): continue # skip variable when search is performed and nothing matches at_least_one_painted = True if not header_is_painted: # always open headers when searching forms.header(group.title(), isopen=search or self._show_only_modified) header_is_painted = True default_value = self._default_values[varname] edit_url = folder_preserving_link([ ("mode", self.edit_mode_name), ("varname", varname), ("site", request.var("site", "")), ]) title = HTMLWriter.render_a( title_text, href=edit_url, class_="modified" if varname in self._current_settings else None, title=escaping.strip_tags(help_text), ) if varname in self._current_settings: value = self._current_settings[varname] elif varname in self._global_settings: value = self._global_settings[varname] else: value = default_value try: to_text = valuespec.value_to_html(value) except Exception: logger.exception("error converting %r to text", value) to_text = html.render_error( _("Failed to render value: %r") % value) # Is this a simple (single) value or not? change styling in these cases... simple = True if "\n" in to_text or "<td>" in to_text: simple = False forms.section(title, simple=simple) if varname in self._current_settings: modified_cls = ["modified"] value_title: Optional[str] = _( "This option has been modified.") elif varname in self._global_settings: modified_cls = ["modified globally"] value_title = _( "This option has been modified in global settings.") else: modified_cls = [] value_title = None if is_a_checkbox(valuespec): html.open_div(class_=["toggle_switch_container"] + modified_cls + (["on"] if value else [])) html.toggle_switch( enabled=value, help_txt=_("Immediately toggle this setting"), href=makeactionuri(request, transactions, [("_action", "toggle"), ("_varname", varname)]), class_=modified_cls, title=value_title, ) html.close_div() else: html.a(to_text, href=edit_url, class_=modified_cls, title=value_title) if header_is_painted: forms.end() if not at_least_one_painted and search: html.show_message( _("Did not find any global setting matching your search.")) html.close_div()