def _render_tag_group(tg_id, tag, object_type, with_link, label_type, label_source): # type: (Union[TagID, Text], Union[TagValue, Text], str, bool, str, str) -> HTML span = html.render_tag(html.render_div( html.render_span("%s:%s" % (tg_id, tag), class_=["tagify__tag-text"])), class_=["tagify--noAnim", label_source]) if not with_link: return span if label_type == "tag_group": type_filter_vars = [ ("%s_tag_0_grp" % object_type, tg_id), ("%s_tag_0_op" % object_type, "is"), ("%s_tag_0_val" % object_type, tag), ] # type: HTTPVariables elif label_type == "label": type_filter_vars = [ ("%s_label" % object_type, ensure_unicode(json.dumps([{ "value": "%s:%s" % (tg_id, tag) }]))), ] else: raise NotImplementedError() url_vars = [ ("filled_in", "filter"), ("search", "Search"), ("view_name", "searchhost" if object_type == "host" else "searchsvc"), ] # type: HTTPVariables url = html.makeuri_contextless(url_vars + type_filter_vars, filename="view.py") return html.render_a(span, href=url)
def unescape_attributes(value): # type: (str) -> Text # In python3 use html.unescape return ensure_unicode(value.replace("&", "&")\ .replace(""", "\"")\ .replace("<", "<")\ .replace(">", ">"))
def _ensure_unicode(self, value): # type: (HTMLInput) -> str # value can of of any type: HTML, int, float, None, str, ... # TODO cleanup call sites if not isinstance(value, six.string_types): value = six.text_type(value) return ensure_unicode(value)
def load(cls): cls.clear_instances() # First load builtin pages. Set username to '' for name, page_dict in cls.builtin_pages().items(): page_dict["owner"] = UserId(u'') # might have been forgotten on copy action page_dict["public"] = True page_dict["name"] = name new_page = cls(page_dict) cls.add_instance(("", name), new_page) # Now scan users subdirs for files "user_$type_name.mk" for user_dir in os.listdir(config.config_dir): user = UserId(ensure_unicode(user_dir)) try: path = "%s/%s/user_%ss.mk" % (config.config_dir, six.ensure_str(user), cls.type_name()) if not os.path.exists(path): continue if not userdb.user_exists(user): continue user_pages = store.load_object_from_file(path, default={}) for name, page_dict in user_pages.items(): page_dict["owner"] = user page_dict["name"] = name cls.add_instance((user, name), cls(page_dict)) except SyntaxError as e: raise MKGeneralException( _("Cannot load %s from %s: %s") % (cls.type_name(), path, e)) cls._load() cls._declare_instance_permissions()
def escape_attribute(value): # type: (Union[None, int, HTML, str, Text]) -> Text """Escape HTML attributes. For example: replace '"' with '"', '<' with '<'. This code is slow. Works on str and unicode without changing the type. Also works on things that can be converted with '%s'. Args: value: Examples: >>> escape_attribute("Hello this is <b>dog</b>!") 'Hello this is <b>dog</b>!' >>> escape_attribute("Hello this is <foo>dog</foo>!") 'Hello this is <foo>dog</foo>!' Returns: """ attr_type = type(value) if value is None: return u'' elif attr_type == int: return six.text_type(value) elif isinstance(value, HTML): return value.__html__() # This is HTML code which must not be escaped elif not isinstance(attr_type, six.string_types): # also possible: type Exception! value = u"%s" % value return ensure_unicode(html_escape(value, quote=True))
def _load_user_scripts_from(adir): scripts = {} if os.path.exists(adir): for entry in os.listdir(adir): entry = ensure_unicode(entry) if entry == ".f12": continue path = adir + "/" + entry if os.path.isfile(path) and os.access(path, os.X_OK): info = {"title": entry, "bulk": False} try: with Path(path).open(encoding="utf-8") as lines: next(lines) line = next(lines).strip() if line.startswith("#") and re.search( r'coding[=:]\s*([-\w.]+)', line): line = next(lines).strip() if line.startswith("#"): info["title"] = line.lstrip("#").strip().split( "#", 1)[0] while True: line = next(lines).strip() if not line.startswith("#") or ":" not in line: break key, value = line[1:].strip().split(":", 1) value = value.strip() if key.lower() == "bulk": info["bulk"] = (value == "yes") except Exception: pass scripts[entry] = info return scripts
def omd_site(): # type: () -> Text try: return ensure_unicode(os.environ["OMD_SITE"]) except KeyError: raise MKGeneralException( _("OMD_SITE environment variable not set. You can " "only execute this in an OMD site."))
def _parse_auth_cookie(cookie_name): # type: (str) -> Tuple[UserId, float, str] raw_cookie = html.request.cookie(cookie_name, "::") assert raw_cookie is not None raw_value = ensure_unicode(raw_cookie) username, issue_time, cookie_hash = raw_value.split(':', 2) return UserId(username), float(issue_time) if issue_time else 0.0, six.ensure_str(cookie_hash)
def _get_agent_info_program(self, commandline, command_stdin): exepath = commandline.split()[0] # for error message, hide options! self._logger.debug("Calling external program %r" % (commandline)) p = None try: if config.monitoring_core == "cmc": p = subprocess.Popen( # nosec commandline, shell=True, stdin=subprocess.PIPE if command_stdin else open(os.devnull), stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid, close_fds=True, encoding="utf-8", ) else: # We can not create a separate process group when running Nagios # Upon reaching the service_check_timeout Nagios only kills the process # group of the active check. p = subprocess.Popen( # nosec commandline, shell=True, stdin=subprocess.PIPE if command_stdin else open(os.devnull), stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, encoding="utf-8", ) if command_stdin: stdout, stderr = p.communicate(input=ensure_unicode(command_stdin)) else: stdout, stderr = p.communicate() exitstatus = p.returncode except MKTimeout: # On timeout exception try to stop the process to prevent child process "leakage" if p: os.killpg(os.getpgid(p.pid), signal.SIGTERM) p.wait() raise finally: # The stdout and stderr pipe are not closed correctly on a MKTimeout # Normally these pipes getting closed after p.communicate finishes # Closing them a second time in a OK scenario won't hurt neither.. if p: p.stdout.close() p.stderr.close() if exitstatus: if exitstatus == 127: raise MKAgentError("Program '%s' not found (exit code 127)" % exepath) else: raise MKAgentError("Agent exited with code %d: %s" % (exitstatus, stderr)) return stdout
def get_optional_package_infos(): # type: () -> Dict[Text, Dict] optional = {} for pkg_path in _get_optional_package_paths(): with pkg_path.open("rb") as pkg: package_info = _get_package_info_from_package(cast(BinaryIO, pkg)) optional[ensure_unicode(pkg_path.name)] = package_info return optional
def check_auth_web_server(request): # type: (Request) -> UserId """Try to get the authenticated user from the HTTP request The user may have configured (basic) authentication by the web server. In case a user is provided, we trust that user. """ user = request.remote_user if user is not None: set_auth_type("web_server") return UserId(ensure_unicode(user))
def get_unicode_input(self, varname, deflt=None): # type: (str, Optional[Text]) -> Optional[Text] try: val = self.var(varname, six.ensure_str(deflt) if deflt is not None else None) if val is None: return None return ensure_unicode(val) except UnicodeDecodeError: raise MKUserError( varname, _("The given text is wrong encoded. " "You need to provide a UTF-8 encoded text."))
def _get_session_id_from_cookie(username): # type: (UserId) -> str raw_value = html.request.cookie(_session_cookie_name(), "::") assert raw_value is not None cookie_username, session_id, cookie_hash = raw_value.split(':', 2) if ensure_unicode(cookie_username) != username \ or cookie_hash != _generate_hash(username, cookie_username + ":" + session_id): auth_logger.error("Invalid session: %s, Cookie: %r" % (username, raw_value)) return "" return session_id
def _check_auth_http_header(): # type: () -> Optional[UserId] """When http header auth is enabled, try to read the user_id from the var and when there is some available, set the auth cookie (for other addons) and proceed.""" assert isinstance(config.auth_by_http_header, str) user_id = html.request.get_request_header(config.auth_by_http_header) if not user_id: return None user_id = UserId(ensure_unicode(user_id)) set_auth_type("http_header") _renew_cookie(auth_cookie_name(), user_id) return user_id
def _validate_tree_path(self, tree_path): # type: (AnyStr) -> None if not tree_path: raise MKGeneralException("Empty tree path or zero.") # TODO: Check if six.binary_type/ensure_unicode is necessary. if not isinstance(tree_path, (six.binary_type, six.text_type)): raise MKGeneralException( "Wrong tree path format. Must be of type string.") tp = ensure_unicode(tree_path) if not tp.endswith((":", ".")): raise MKGeneralException("No valid tree path.") if bool(re.compile('[^a-zA-Z0-9_.:-]').search(tp)): raise MKGeneralException( "Specified tree path contains unexpected characters.")
def lq_logic(filter_condition, values, join): # type: (str, Union[AnyStr, List[AnyStr]], str) -> Text """JOIN with (Or, And) FILTER_CONDITION the VALUES for a livestatus query""" if isinstance(values, six.string_types): values = [values] conds = [ u"%s %s" % (filter_condition, livestatus.lqencode(x)) for x in values ] if len(conds) > 1: return ensure_unicode("\n".join(conds) + "\n%s: %d\n" % (join, len(conds))) if conds: return conds[0] + u'\n' return u""
def format_plugin_output(output, row=None, shall_escape=True): # type: (CellContent, Optional[Row], bool) -> Text assert not isinstance(output, dict) ok_marker = '<b class="stmark state0">OK</b>' warn_marker = '<b class="stmark state1">WARN</b>' crit_marker = '<b class="stmark state2">CRIT</b>' unknown_marker = '<b class="stmark state3">UNKN</b>' # In case we have a host or service row use the optional custom attribute # ESCAPE_PLUGIN_OUTPUT (set by host / service ruleset) to override the global # setting. if row: custom_vars = row.get("service_custom_variables", row.get("host_custom_variables", {})) if "ESCAPE_PLUGIN_OUTPUT" in custom_vars: shall_escape = custom_vars["ESCAPE_PLUGIN_OUTPUT"] == "1" if shall_escape: output = escaping.escape_attribute(output) else: output = ensure_unicode("%s" % output) output = output.replace("(!)", warn_marker) \ .replace("(!!)", crit_marker) \ .replace("(?)", unknown_marker) \ .replace("(.)", ok_marker) if row and "[running on" in output: a = output.index("[running on") e = output.index("]", a) hosts = output[a + 12:e].replace(" ", "").split(",") h = get_host_list_links(row["site"], hosts) output = output[:a] + "running on " + ", ".join(h) + output[e + 1:] if shall_escape: http_url = r"(http[s]?://[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+)" # (?:<A HREF="), (?: target="_blank">)? and endswith(" </A>") is a special # handling for the HTML code produced by check_http when "clickable URL" option is active. output = re.sub( "(?:<A HREF=")?" + http_url + "(?: target="_blank">)?", lambda p: str( html.render_icon_button( p.group(1).replace('"', ''), p.group(1).replace('"', ''), "link")), output) if output.endswith(" </A>"): output = output[:-11] return output
def _cleanup_old_user_profiles(updated_profiles): # type: (Users) -> None profile_files_to_delete = [ "automation.secret", "transids.mk", "serial.mk", ] directory = cmk.utils.paths.var_dir + "/web" for user_dir in os.listdir(cmk.utils.paths.var_dir + "/web"): if user_dir not in ['.', '..'] and ensure_unicode(user_dir) not in updated_profiles: entry = directory + "/" + user_dir if not os.path.isdir(entry): continue for to_delete in profile_files_to_delete: if os.path.exists(entry + '/' + to_delete): os.unlink(entry + '/' + to_delete)
def _get_local_vars_of_last_exception(): # type: () -> Text local_vars = {} try: for key, val in inspect.trace()[-1][0].f_locals.items(): local_vars[key] = _format_var_for_export(val) except IndexError: # please don't crash in the attempt to report a crash. # Don't know why inspect.trace() causes an IndexError but it does happen pass # This needs to be encoded as the local vars might contain binary data which can not be # transported using JSON. return ensure_unicode( base64.b64encode( _format_var_for_export(pprint.pformat(local_vars).encode("utf-8"), maxsize=5 * 1024 * 1024)))
def _upload_csv_file(self): store.makedirs(self._upload_tmp_path) self._cleanup_old_files() upload_info = self._vs_upload().from_html_vars("_upload") self._vs_upload().validate_value(upload_info, "_upload") _file_name, _mime_type, content = upload_info["file"] file_id = "%s-%d" % (config.user.id, int(time.time())) store.save_text_to_file(self._file_path(), ensure_unicode(content)) # make selections available to next page html.request.set_var("file_id", file_id) if upload_info["do_service_detection"]: html.request.set_var("do_service_detection", "1")
def output(text, *args, **kwargs): # type: (AnyStr, *Any, **IO[Any]) -> None if args: text = text % args if six.PY3: ensured_text = ensure_unicode(text) # type: Text else: ensured_text = ensure_bytestr(text) # type: bytes stream = kwargs.get("stream", sys.stdout) try: stream.write(ensured_text) stream.flush() except Exception: # TODO: Way to generic! pass # avoid exception on broken pipe (e.g. due to | head)
def bearer_auth(token): # type: (str) -> Optional[RFC7662] try: user_id, secret = token.split(' ', 1) except ValueError: return None if not secret: return None if not user_id: return None if "/" in user_id: return None if verify_automation_secret(UserId(ensure_unicode(user_id)), secret): # Auth with automation secret succeeded - mark transid as unneeded in this case return _subject(user_id) return None
def strip_tags(ht): # type: (Union[HTML, str, Text]) -> Text """Strip all HTML tags from a text. Args: ht: A text with possible HTML tags in it. Examples: >>> strip_tags("<b>foobar</b> blah") 'foobar blah' Edge cases. >>> strip_tags("<p<b<>re>foobar</</b>b> blah") 're>foobarb> blah' Returns: A string without working HTML tags. """ if isinstance(ht, HTML): ht = ht.__html__() if not isinstance(ht, six.string_types): return u"%s" % ht ht = ensure_unicode(ht) while True: x = ht.find('<') if x == -1: break y = ht.find('>', x) if y == -1: break ht = ht[0:x] + ht[y + 1:] return ht.replace(" ", " ")
def log_entry(linkinfo, action, message, user_id=None): # TODO: Create a more generic referencing # linkinfo identifies the object operated on. It can be a Host or a Folder # or a text. # linkinfo is either a Folder, or a Host or a hostname or None if hasattr(linkinfo, "linkinfo"): link = linkinfo.linkinfo() else: link = linkinfo write_tokens = ( time.strftime("%s"), link or "-", user_id or config.user.id or "-", action, message.replace("\n", "\\n"), ) write_tokens = (ensure_unicode(t) for t in write_tokens) store.makedirs(audit_log_path.parent) with audit_log_path.open(mode="a", encoding='utf-8') as f: audit_log_path.chmod(0o660) f.write(u" ".join(write_tokens) + u"\n")
def omd_version(): # type: () -> Text version_link = Path(cmk.utils.paths.omd_root).joinpath("version") return ensure_unicode(version_link.resolve().name) # pylint: disable=no-member
def test_ensure_unicode(source, utf8str): assert ensure_unicode(source) == utf8str
def _generate_secret(): # type: () -> Text return ensure_unicode(utils.get_random_string(256))
def _(string): # type: (AnyStr) -> str return ensure_unicode(string)
def write_package_info(package): # type: (PackageInfo) -> None pkg_info_path = package_dir() / package["name"] with pkg_info_path.open("w", encoding="utf-8") as f: f.write(ensure_unicode(pprint.pformat(package) + "\n"))
def load_users(lock=False): # type: (bool) -> Users filename = _root_dir() + "contacts.mk" if lock: # Note: the lock will be released on next save_users() call or at # end of page request automatically. store.aquire_lock(filename) if 'users' in g: return g.users # First load monitoring contacts from Check_MK's world. If this is # the first time, then the file will be empty, which is no problem. # Execfile will the simply leave contacts = {} unchanged. contacts = store.load_from_mk_file(filename, "contacts", {}) # Now load information about users from the GUI config world filename = _multisite_dir() + "users.mk" users = store.load_from_mk_file(_multisite_dir() + "users.mk", "multisite_users", {}) # Merge them together. Monitoring users not known to Multisite # will be added later as normal users. result = {} for uid, user in users.items(): # Transform user IDs which were stored with a wrong type uid = ensure_unicode(uid) profile = contacts.get(uid, {}) profile.update(user) result[uid] = profile # Convert non unicode mail addresses if "email" in profile: profile["email"] = ensure_unicode(profile["email"]) # This loop is only neccessary if someone has edited # contacts.mk manually. But we want to support that as # far as possible. for uid, contact in contacts.items(): # Transform user IDs which were stored with a wrong type uid = ensure_unicode(uid) if uid not in result: result[uid] = contact result[uid]["roles"] = ["user"] result[uid]["locked"] = True result[uid]["password"] = "" # Passwords are read directly from the apache htpasswd-file. # That way heroes of the command line will still be able to # change passwords with htpasswd. Users *only* appearing # in htpasswd will also be loaded and assigned to the role # they are getting according to the multisite old-style # configuration variables. def readlines(f): try: return Path(f).open(encoding="utf-8") except IOError: return [] # FIXME TODO: Consolidate with htpasswd user connector filename = cmk.utils.paths.htpasswd_file for line in readlines(filename): line = line.strip() if ':' in line: uid, password = line.strip().split(":")[:2] uid = ensure_unicode(uid) if password.startswith("!"): locked = True password = password[1:] else: locked = False if uid in result: result[uid]["password"] = password result[uid]["locked"] = locked else: # Create entry if this is an admin user new_user = { "roles": config.roles_of_user(uid), "password": password, "locked": False, } result[uid] = new_user # Make sure that the user has an alias result[uid].setdefault("alias", uid) # Other unknown entries will silently be dropped. Sorry... # Now read the serials, only process for existing users serials_file = '%s/auth.serials' % os.path.dirname( cmk.utils.paths.htpasswd_file) for line in readlines(serials_file): line = line.strip() if ':' in line: user_id, serial = line.split(':')[:2] user_id = ensure_unicode(user_id) if user_id in result: result[user_id]['serial'] = utils.saveint(serial) # Now read the user specific files directory = cmk.utils.paths.var_dir + "/web/" for d in os.listdir(directory): if d[0] != '.': uid = ensure_unicode(d) # read special values from own files if uid in result: for attr, conv_func in [ ('num_failed_logins', utils.saveint), ('last_pw_change', utils.saveint), ('last_seen', utils.savefloat), ('enforce_pw_change', lambda x: bool(utils.saveint(x))), ('idle_timeout', _convert_idle_timeout), ('session_id', _convert_session_info), ]: val = load_custom_attr(uid, attr, conv_func) if val is not None: result[uid][attr] = val # read automation secrets and add them to existing # users or create new users automatically try: user_secret_path = Path(directory) / d / "automation.secret" with user_secret_path.open(encoding="utf-8") as f: secret = six.ensure_str( f.read().strip()) # type: Optional[str] except IOError: secret = None if secret: if uid in result: result[uid]["automation_secret"] = secret else: result[uid] = { "roles": ["guest"], "automation_secret": secret, } # populate the users cache g.users = result return result