def _show_form(self) -> None: assert user.id is not None credentials = load_two_factor_credentials(user.id) webauthn_credentials = credentials["webauthn_credentials"] backup_codes = credentials["backup_codes"] html.begin_form("two_factor", method="POST") html.div("", id_="webauthn_message") forms.header(_("Credentials")) forms.section(_("Registered credentials"), simple=True) if webauthn_credentials: self._show_credentials(webauthn_credentials) else: html.i(_("No credentials registered")) forms.section(_("Backup codes"), simple=True) if backup_codes: html.p( _("You have %d unused backup codes left. You can use them as one-time password " "if your key is not available.") % len(backup_codes)) html.i( _("If you regenerate backup codes, you automatically invalidate the existing codes." )) else: html.i(_("No backup codes created yet.")) forms.end() html.hidden_fields() html.end_form() html.footer()
def page(self): host_names = get_hostnames_from_checkboxes() hosts = dict([ (host_name, watolib.Folder.current().host(host_name)) for host_name in host_names ]) current_host_hash = sha256(six.ensure_binary(repr(hosts))) # When bulk edit has been made with some hosts, then other hosts have been selected # and then another bulk edit has made, the attributes need to be reset before # rendering the form. Otherwise the second edit will have the attributes of the # first set. host_hash = html.request.var("host_hash") if not host_hash or host_hash != current_host_hash: html.request.del_vars(prefix="attr_") html.request.del_vars(prefix="bulk_change_") html.p("%s%s %s" % (_("You have selected <b>%d</b> hosts for bulk edit. You can now change " "host attributes for all selected hosts at once. ") % len(hosts), _("If a select is set to <i>don't change</i> then currenty not all selected " "hosts share the same setting for this attribute. " "If you leave that selection, all hosts will keep their individual settings."), _("In case you want to <i>unset</i> attributes on multiple hosts, you need to " "use the <i>bulk cleanup</i> action instead of bulk edit."))) html.begin_form("edit_host", method="POST") html.prevent_password_auto_completion() html.hidden_field("host_hash", current_host_hash) configure_attributes(False, hosts, "bulk", parent=watolib.Folder.current()) forms.end() html.button("_save", _("Save & Finish")) html.hidden_fields() html.end_form()
def write_snapin_exception(e): html.open_div(class_=["snapinexception"]) html.h2(_('Error')) html.p(e) html.div(traceback.format_exc().replace('\n', '<br>'), style="display:none;") html.close_div()
def page(self): html.p( _("This module can be used to define conditions for Check_MK rules in a central place. " "You can then refer to these conditions from different rulesets. Using these predefined " "conditions may save you a lot of redundant conditions when you need them in multiple " "rulesets.")) super(ModePredefinedConditions, self).page()
def write_snapin_exception(e: Exception) -> None: html.open_div(class_=["snapinexception"]) html.h2(_("Error")) html.p(str(e)) html.div(traceback.format_exc().replace("\n", "<br>"), style="display:none;") html.close_div()
def page(self) -> None: html.h3(_("Upload Icon")) html.p( _("Allowed are single PNG image files with a maximum size of 80x80 px." )) html.begin_form('upload_form', method='POST') self._vs_upload().render_input('_upload_icon', None) html.hidden_fields() html.end_form() icons = sorted(self._load_custom_icons().items()) with table_element("icons", _("Custom Icons")) as table: for icon_name, category_name in icons: table.row() table.cell(_("Actions"), css="buttons") delete_url = make_action_link([("mode", "icons"), ("_delete", icon_name)]) html.icon_button(delete_url, _("Delete this Icon"), "delete") table.cell(_("Icon"), html.render_icon(icon_name), css="buttons") table.text_cell(_("Name"), icon_name) table.text_cell(_("Category"), IconSelector.category_alias(category_name))
def page(self) -> None: html.p( _("Here you can add icons, for example to use them in bookmarks or " "in custom actions of views. Allowed are single PNG image files " "with a maximum size of 80x80 px. Custom actions have to be defined " "in the global settings and can be used in the custom icons rules " "of hosts and services.")) html.begin_form('upload_form', method='POST') self._vs_upload().render_input('_upload_icon', None) html.hidden_fields() html.end_form() icons = sorted(self._load_custom_icons().items()) with table_element("icons", _("Custom Icons")) as table: for icon_name, category_name in icons: table.row() table.cell(_("Actions"), css="buttons") delete_url = make_confirm_link( url=make_action_link([("mode", "icons"), ("_delete", icon_name)]), message=_( "Do you really want to delete the icon <b>%s</b>?") % icon_name, ) html.icon_button(delete_url, _("Delete this Icon"), "delete") table.cell(_("Icon"), html.render_icon(icon_name), css="buttons") table.cell(_("Name"), icon_name) table.cell(_("Category"), IconSelector.category_alias(category_name))
def _show_form(self, profile_changed: bool) -> None: assert config.user.id is not None users = userdb.load_users() change_reason = html.request.get_ascii_input('reason') if change_reason == 'expired': html.p( _('Your password is too old, you need to choose a new password.' )) elif change_reason == 'enforced': html.p( _('You are required to change your password before proceeding.' )) if profile_changed: html.show_message(_("Your password has been changed.")) if change_reason: raise HTTPRedirect( html.request.get_str_input_mandatory( '_origtarget', 'index.py')) if html.has_user_errors(): html.show_user_errors() user = users.get(config.user.id) if user is None: html.show_warning(_("Sorry, your user account does not exist.")) html.footer() return locked_attributes = userdb.locked_attributes(user.get('connector')) if "password" in locked_attributes: raise MKUserError( "cur_password", _("You can not change your password, because it is " "managed by another system.")) html.begin_form("profile", method="POST") html.prevent_password_auto_completion() html.open_div(class_="wato") forms.header(self._page_title()) forms.section(_("Current Password")) html.password_input('cur_password', autocomplete="new-password") forms.section(_("New Password")) html.password_input('password', autocomplete="new-password") forms.section(_("New Password Confirmation")) html.password_input('password2', autocomplete="new-password") forms.end() html.close_div() html.hidden_fields() html.end_form() html.footer()
def page(self): html.p( _("The WATO configuration can be set to read only mode for all users that are not " "permitted to ignore the read only mode. All users that are permitted to set the " "read only can disable it again when another permitted user enabled it before.")) html.begin_form("read_only", method="POST") self._vs().render_input("_read_only", self._settings) html.hidden_fields() html.end_form()
def show_details(self, crash_info, row): if not crash_info["details"]: return html.h2(_("Details")) html.p( _("No detail renderer for crash of type '%s' available. Details structure is:" ) % crash_info["crash_type"]) html.pre(pprint.pformat(crash_info["details"]))
def show_details(self, crash_info: CrashInfo, row) -> None: if not crash_info["details"]: return html.h3(_("Details"), class_="table") html.p( _("No detail renderer for crash of type '%s' available. Details structure is:" ) % crash_info["crash_type"]) html.pre(pprint.pformat(crash_info["details"]))
def _show_backup_codes(self, backup_codes: Sequence[str]) -> None: forms.header(_("Backup codes")) forms.section(_("Backup codes"), simple=True) if backup_codes: html.p(_("You have %d unused backup codes left.") % len(backup_codes)) html.i(_("If you regenerate backup codes, you automatically invalidate old codes.")) else: html.i(_("No backup codes created yet.")) forms.end()
def show(self): self._load() if not config.virtual_host_trees: url = 'wato.py?varname=virtual_host_trees&mode=edit_configvar' html.p( _('You have not defined any virtual host trees. You can do this ' 'in the <a href="%s" target="main">global settings</a>.') % url) return self._show_tree_selection() self._show_tree()
def _upload_form(self) -> None: html.begin_form("upload", method="POST") html.p( _("Using this page you can import several hosts at once into the choosen folder. You can " "choose a CSV file from your workstation to be uploaded, paste a CSV files contents " "into the textarea or simply enter a list of hostnames (one per line) to the textarea." )) self._vs_upload().render_input("_upload", None) html.hidden_fields() html.end_form()
def page(self): html.p( _("To be able to download the key, you need to unlock the key by entering the " "passphrase. This is only done to verify that you are allowed to download the key. " "The key will be downloaded in encrypted form.")) html.begin_form("key", method="POST") html.prevent_password_auto_completion() self._vs_key().render_input("key", {}) self._vs_key().set_focus("key") html.hidden_fields() html.end_form()
def page(self): html.p( _("This password management module stores the passwords you use in your checks and " "special agents in a central place. Please note that this password store is no " "kind of password safe. Your passwords will not be encrypted.")) html.p( _("All the passwords you store in your monitoring configuration, " "including this password store, are needed in plain text to contact remote systems " "for monitoring. So all those passwords have to be stored readable by the monitoring." )) super(ModePasswords, self).page()
def page(self): html.p( _('This page can be used to generate a new timeperiod definition based ' 'on the appointments of an iCalendar (<tt>*.ics</tt>) file. This import is normally used ' 'to import events like holidays, therefore only single whole day appointments are ' 'handled by this import.')) html.begin_form("import_ical", method="POST") self._vs_ical().render_input("ical", {}) forms.end() html.hidden_fields() html.end_form()
def user_profile_async_replication_dialog(sites: List[SiteId], back_url: str) -> None: html.p( _( "In order to activate your changes available on all remote sites, your user profile needs " "to be replicated to the remote sites. This is done on this page now. Each site " "is being represented by a single image which is first shown gray and then fills " "to green during synchronisation." ) ) html.h3(_("Replication States")) html.open_div(id_="profile_repl") num_replsites = 0 for site_id in sites: site = config.sites[site_id] if "secret" not in site: status_txt = _("Not logged in.") start_sync = False icon = "repl_locked" else: status_txt = _("Waiting for replication to start") start_sync = True icon = "repl_pending" html.open_div(class_="site", id_="site-%s" % site_id) html.div("", title=status_txt, class_=["icon", "repl_status", icon]) if start_sync: changes_manager = watolib.ActivateChanges() changes_manager.load() estimated_duration = changes_manager.get_activation_time( site_id, ACTIVATION_TIME_PROFILE_SYNC, 2.0 ) html.javascript( "cmk.profile_replication.start(%s, %d, %s);" % ( json.dumps(site_id), int(estimated_duration * 1000.0), json.dumps(_("Replication in progress")), ) ) num_replsites += 1 else: _add_profile_replication_change(site_id, status_txt) html.span(site.get("alias", site_id)) html.close_div() html.javascript( "cmk.profile_replication.prepare(%d, %s);\n" % (num_replsites, json.dumps(back_url)) ) html.close_div()
def jqm_page_index_topic_renderer(topic: str, items: Items) -> None: has_items_for_topic = any(i for i in items if i[0] == topic) if not has_items_for_topic: return html.p(topic) html.open_ul(**{"data-role": "listview", "data-inset": "true"}) for top, href, title in items: if top == topic: html.open_li() html.a(title, href=href, **{"data-ajax": "false", "data-transition": "flip"}) html.close_li() html.close_ul()
def _show_form(self) -> None: assert user.id is not None users = userdb.load_users() change_reason = request.get_ascii_input("reason") if change_reason == "expired": html.p( _("Your password is too old, you need to choose a new password." )) elif change_reason == "enforced": html.p( _("You are required to change your password before proceeding." )) user_spec = users.get(user.id) if user_spec is None: html.show_warning(_("Sorry, your user account does not exist.")) html.footer() return locked_attributes = userdb.locked_attributes( user_spec.get("connector")) if "password" in locked_attributes: raise MKUserError( "cur_password", _("You can not change your password, because it is " "managed by another system."), ) html.begin_form("profile", method="POST") html.prevent_password_auto_completion() html.open_div(class_="wato") forms.header(self._page_title()) forms.section(_("Current Password")) html.password_input("cur_password", autocomplete="new-password") forms.section(_("New Password")) html.password_input("password", autocomplete="new-password") forms.section(_("New Password Confirmation")) html.password_input("password2", autocomplete="new-password") forms.end() html.close_div() html.hidden_fields() html.end_form() html.footer()
def page(self): hosts = get_hosts_from_checkboxes() html.p( _("You have selected <b>%d</b> hosts for bulk cleanup. This means removing " "explicit attribute values from hosts. The hosts will then inherit attributes " "configured at the host list or folders or simply fall back to the builtin " "default values.") % len(hosts)) html.begin_form("bulkcleanup", method="POST") forms.header(_("Attributes to remove from hosts")) self._select_attributes_for_bulk_cleanup(hosts) html.hidden_fields() html.end_form()
def render_werk_description(werk) -> HTML: with output_funnel.plugged(): html.open_p() in_list = False in_code = False for line in werk["body"]: if line.startswith("LI:"): if not in_list: html.open_ul() in_list = True html.li(line[3:]) else: if in_list: html.close_ul() in_list = False if line.startswith("H2:"): html.h3(line[3:]) elif line.startswith("C+:"): html.open_pre(class_="code") in_code = True elif line.startswith("F+:"): file_name = line[3:] if file_name: html.div(file_name, class_="filename") html.open_pre(class_="file") in_code = True elif line.startswith("C-:") or line.startswith("F-:"): html.close_pre() in_code = False elif line.startswith("OM:"): html.write_text("OMD[mysite]:~$ ") html.b(line[3:]) elif line.startswith("RP:"): html.write_text("root@myhost:~# ") html.b(line[3:]) elif not line.strip() and not in_code: html.p("") else: html.write_text(line + "\n") if in_list: html.close_ul() html.close_p() return HTML(output_funnel.drain())
def page(self) -> None: row = self._get_crash_row() crash_info = self._get_crash_info(row) title = _("Crash report: %s") % self._crash_id breadcrumb = self._breadcrumb(title) html.header(title, breadcrumb, self._page_menu(breadcrumb, crash_info)) # Do not reveal crash context information to unauthenticated users or not permitted # users to prevent disclosure of internal information if not user.may("general.see_crash_reports"): html.show_error("<b>%s:</b> %s" % (_("Internal error"), crash_info["exc_value"])) html.p( _( "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>." ) ) html.footer() return if request.has_var("_report") and transactions.check_transaction(): details = self._handle_report_form(crash_info) else: details = ReportSubmitDetails(name="", mail="") if crash_info["crash_type"] == "gui": html.show_error("<b>%s:</b> %s" % (_("Internal error"), crash_info["exc_value"])) html.p( _( "An internal error occured while processing your request. " "You can report this issue to the Checkmk team to help " "fixing this issue. Please use the form below for reporting." ) ) self._warn_about_local_files(crash_info) self._show_report_form(crash_info, details) self._show_crash_report(crash_info) self._show_crash_report_details(crash_info, row) html.footer()
def user_profile_async_replication_dialog(sites): html.p( _('In order to activate your changes available on all remote sites, your user profile needs ' 'to be replicated to the remote sites. This is done on this page now. Each site ' 'is being represented by a single image which is first shown gray and then fills ' 'to green during synchronisation.')) html.h3(_('Replication States')) html.open_div(id_="profile_repl") num_replsites = 0 for site_id in sites: site = config.sites[site_id] if "secret" not in site: status_txt = _('Not logged in.') start_sync = False icon = 'repl_locked' else: status_txt = _('Waiting for replication to start') start_sync = True icon = 'repl_pending' html.open_div(class_="site", id_="site-%s" % site_id) html.div("", title=status_txt, class_=["icon", "repl_status", icon]) if start_sync: changes_manager = watolib.ActivateChanges() changes_manager.load() estimated_duration = changes_manager.get_activation_time( site_id, ACTIVATION_TIME_PROFILE_SYNC, 2.0) html.javascript( 'cmk.profile_replication.start(\'%s\', %d, \'%s\');' % (site_id, int(estimated_duration * 1000.0), _('Replication in progress'))) num_replsites += 1 else: _add_profile_replication_change(site_id, status_txt) html.span(site.get('alias', site_id)) html.close_div() html.javascript('cmk.profile_replication.prepare(%d);\n' % num_replsites) html.close_div()
def render_mobile_list(rows, view, group_cells, cells, num_columns, show_checkboxes): if not is_mobile(request, response): html.show_error(_("This view can only be used in mobile mode.")) return # Force relative timestamp always. This saves space. painter_options = PainterOptions.get_instance() painter_options.set("ts_format", "rel") html.open_ul(class_="mobilelist", **{"data-role": "listview"}) # Paint data rows for row in rows: html.open_li() rendered_cells = [cell.render(row) for cell in cells] if rendered_cells: # First cell (assumedly state) is left rendered_class, rendered_content = rendered_cells[0] html.p(rendered_content, class_=["ui-li-aside", "ui-li-desc", rendered_class]) if len(rendered_cells) > 1: content = HTML(" · ").join([ rendered_cell[1] for rendered_cell in rendered_cells[1:num_columns + 1] ]) html.h3(content) for rendered_cell, cell in zip( rendered_cells[num_columns + 1:], cells[num_columns + 1:]): rendered_class, rendered_content = rendered_cell html.open_p(class_="ui-li-desc") cell.paint_as_header() html.write_text(': ') html.span(rendered_content, class_=rendered_class) html.close_p() html.close_li() html.close_ul() html.javascript('$("ul.mobilelist a").attr("data-ajax", "false");')
def _show_start_form(self): html.begin_form("parentscan", method="POST") html.hidden_fields() # Mode of action html.open_p() if not self._complete_folder: num_selected = len(get_hosts_from_checkboxes()) html.write_text( _("You have selected <b>%d</b> hosts for parent scan. ") % num_selected) html.p( _("The parent scan will try to detect the last gateway " "on layer 3 (IP) before a host. This will be done by " "calling <tt>traceroute</tt>. If a gateway is found by " "that way and its IP address belongs to one of your " "monitored hosts, that host will be used as the hosts " "parent. If no such host exists, an artifical ping-only " "gateway host will be created if you have not disabled " "this feature.")) forms.header(_("Settings for Parent Scan")) self._settings = config.user.load_file( "parentscan", { "where": "subfolder", "alias": _("Created by parent scan"), "recurse": True, "select": "noexplicit", "timeout": 8, "probes": 2, "ping_probes": 5, "max_ttl": 10, "force_explicit": False, }) # Selection forms.section(_("Selection")) if self._complete_folder: html.checkbox("recurse", self._settings["recurse"], label=_("Include all subfolders")) html.br() html.radiobutton( "select", "noexplicit", self._settings["select"] == "noexplicit", _("Skip hosts with explicit parent definitions (even if empty)") + "<br>") html.radiobutton( "select", "no", self._settings["select"] == "no", _("Skip hosts hosts with non-empty parents (also if inherited)") + "<br>") html.radiobutton("select", "ignore", self._settings["select"] == "ignore", _("Scan all hosts") + "<br>") # Performance forms.section(_("Performance")) html.open_table() html.open_tr() html.open_td() html.write_text(_("Timeout for responses") + ":") html.close_td() html.open_td() html.number_input("timeout", self._settings["timeout"], size=2) html.write_text(_("sec")) html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Number of probes per hop") + ":") html.close_td() html.open_td() html.number_input("probes", self._settings["probes"], size=2) html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Maximum distance (TTL) to gateway") + ":") html.close_td() html.open_td() html.number_input("max_ttl", self._settings["max_ttl"], size=2) html.close_td() html.close_tr() html.open_tr() html.open_td() html.write_text(_("Number of PING probes") + ":") html.help( _("After a gateway has been found, Check_MK checks if it is reachable " "via PING. If not, it is skipped and the next gateway nearer to the " "monitoring core is being tried. You can disable this check by setting " "the number of PING probes to 0.")) html.close_td() html.open_td() html.number_input("ping_probes", self._settings.get("ping_probes", 5), size=2) html.close_td() html.close_tr() html.close_table() # Configuring parent forms.section(_("Configuration")) html.checkbox( "force_explicit", self._settings["force_explicit"], label= _("Force explicit setting for parents even if setting matches that of the folder" )) # Gateway creation forms.section(_("Creation of gateway hosts")) html.write_text(_("Create gateway hosts in")) html.open_ul() html.radiobutton( "where", "subfolder", self._settings["where"] == "subfolder", _("in the subfolder <b>%s/Parents</b>") % watolib.Folder.current_disk_folder().title()) html.br() html.radiobutton( "where", "here", self._settings["where"] == "here", _("directly in the folder <b>%s</b>") % watolib.Folder.current_disk_folder().title()) html.br() html.radiobutton("where", "there", self._settings["where"] == "there", _("in the same folder as the host")) html.br() html.radiobutton("where", "nowhere", self._settings["where"] == "nowhere", _("do not create gateway hosts")) html.close_ul() html.write_text(_("Alias for created gateway hosts") + ": ") html.text_input("alias", self._settings["alias"]) forms.end() # Start button html.button("_start", _("Start")) html.hidden_fields() html.end_form()
def page_crashed(what): # Do not reveal crash context information to unauthenticated users or not permitted # users to prevent disclosure of internal information if not config.user.may("general.see_crash_reports"): html.header(_("Internal error")) html.show_error("<b>%s:</b> %s" % (_("Internal error"), sys.exc_info()[1])) html.p( _("An internal error occurred while processing your request. " "You can report this issue to your Check_MK administrator. " "Detailed information can be found in <tt>var/log/web.log</tt>.") ) html.footer() return if what == "check": site = html.request.var("site") host = html.request.var("host") service = html.request.var("service") tardata = get_crash_report_archive_as_string(site, host, service) else: tardata = create_gui_crash_report(what) info = get_crash_info(tardata) if what == "check": title = _("Crashed Check Reporting") else: title = _("Internal error") html.header(title) show_context_buttons(what, tardata) if html.request.has_var("_report") and html.check_transaction(): details = handle_report_form(tardata, what) else: details = {} if what == "gui": # Unify different string types from exception messages to a unicode string exc_value = sys.exc_info()[1] try: exc_txt = unicode(exc_value) except UnicodeDecodeError: exc_txt = str(exc_value).decode("utf-8") html.show_error("<b>%s:</b> %s" % (_("Internal error"), exc_txt)) html.p( _("An internal error occured while processing your request. " "You can report this issue to the Check_MK team to help " "fixing this issue. Please use the form below for reporting.")) if info: warn_about_local_files(info) show_report_form(what, details) show_crash_report(info) show_crash_report_details(info) else: report_url = html.makeuri( [("subject", "Check_MK Crash Report - " + get_version(what))], filename="mailto:" + get_crash_report_target(what)) html.message( _("This crash report is in a legacy format and can not be submitted " "automatically. Please download it manually and send it to <a href=\"%s\">%s</a>" ) % (report_url, get_crash_report_target(what))) show_old_dump_trace(tardata) show_agent_output(tardata) html.footer()
def _show_page_user_profile(change_pw): start_async_replication = False if not config.user.id: raise MKUserError(None, _('Not logged in.')) if not config.user.may('general.edit_profile') and not config.user.may( 'general.change_password'): raise MKAuthException( _("You are not allowed to edit your user profile.")) if not config.wato_enabled: raise MKAuthException( _('User profiles can not be edited (WATO is disabled).')) success = None if html.request.has_var('_save') and html.check_transaction(): users = userdb.load_users(lock=True) try: # Profile edit (user options like language etc.) if config.user.may('general.edit_profile'): if not change_pw: set_lang = html.get_checkbox('_set_lang') language = html.request.var('language') # Set the users language if requested if set_lang: if language == '': language = None # Set custom language users[config.user.id]['language'] = language config.user.language = language html.set_language_cookie(language) else: # Remove the customized language if 'language' in users[config.user.id]: del users[config.user.id]['language'] config.user.reset_language() # load the new language cmk.gui.i18n.localize(config.user.language) user = users.get(config.user.id) if config.user.may('general.edit_notifications' ) and user.get("notifications_enabled"): value = forms.get_input( watolib.get_vs_flexible_notifications(), "notification_method") users[config.user.id]["notification_method"] = value # Custom attributes if config.user.may('general.edit_user_attributes'): for name, attr in userdb.get_user_attributes(): if attr.user_editable(): if not attr.permission() or config.user.may( attr.permission()): vs = attr.valuespec() value = vs.from_html_vars('ua_' + name) vs.validate_value(value, "ua_" + name) users[config.user.id][name] = value # Change the password if requested password_changed = False if config.user.may('general.change_password'): cur_password = html.request.var('cur_password') password = html.request.var('password') password2 = html.request.var('password2', '') if change_pw: # Force change pw mode if not cur_password: raise MKUserError( "cur_password", _("You need to provide your current password.")) if not password: raise MKUserError( "password", _("You need to change your password.")) if cur_password == password: raise MKUserError( "password", _("The new password must differ from your current one." )) if cur_password and password: if userdb.hook_login(config.user.id, cur_password) is False: raise MKUserError("cur_password", _("Your old password is wrong.")) if password2 and password != password2: raise MKUserError( "password2", _("The both new passwords do not match.")) watolib.verify_password_policy(password) users[config.user.id]['password'] = hash_password(password) users[config.user.id]['last_pw_change'] = int(time.time()) if change_pw: # Has been changed, remove enforcement flag del users[config.user.id]['enforce_pw_change'] # Increase serial to invalidate old cookies if 'serial' not in users[config.user.id]: users[config.user.id]['serial'] = 1 else: users[config.user.id]['serial'] += 1 password_changed = True # Now, if in distributed environment where users can login to remote sites, # set the trigger for pushing the new auth information to the slave sites # asynchronous if config.user.authorized_login_sites(): start_async_replication = True userdb.save_users(users) if password_changed: # Set the new cookie to prevent logout for the current user login.set_auth_cookie(config.user.id) success = True except MKUserError as e: html.add_user_error(e.varname, e) else: users = userdb.load_users() watolib.init_wato_datastructures(with_wato_lock=True) # When in distributed setup, display the replication dialog instead of the normal # profile edit dialog after changing the password. if start_async_replication: user_profile_async_replication_page() return if change_pw: title = _("Change Password") else: title = _("Edit User Profile") html.header(title) # Rule based notifications: The user currently cannot simply call the according # WATO module due to WATO permission issues. So we cannot show this button # right now. if not change_pw: rulebased_notifications = watolib.load_configuration_settings().get( "enable_rulebased_notifications") if rulebased_notifications and config.user.may( 'general.edit_notifications'): html.begin_context_buttons() url = "wato.py?mode=user_notifications_p" html.context_button(_("Notifications"), url, "notifications") html.end_context_buttons() else: reason = html.request.var('reason') if reason == 'expired': html.p( _('Your password is too old, you need to choose a new password.' )) else: html.p( _('You are required to change your password before proceeding.' )) if success: html.reload_sidebar() if change_pw: html.show_message(_("Your password has been changed.")) raise HTTPRedirect(html.request.var('_origtarget', 'index.py')) else: html.show_message(_("Successfully updated user profile.")) # Ensure theme changes are applied without additional user interaction html.immediate_browser_redirect(0.5, html.makeuri([])) if html.has_user_errors(): html.show_user_errors() user = users.get(config.user.id) if user is None: html.show_warning(_("Sorry, your user account does not exist.")) html.footer() return # Returns true if an attribute is locked and should be read only. Is only # checked when modifying an existing user locked_attributes = userdb.locked_attributes(user.get('connector')) def is_locked(attr): return attr in locked_attributes html.begin_form("profile", method="POST") html.prevent_password_auto_completion() html.open_div(class_="wato") forms.header(_("Personal Settings")) if not change_pw: forms.section(_("Name"), simple=True) html.write_text(user.get("alias", config.user.id)) if config.user.may( 'general.change_password') and not is_locked('password'): forms.section(_("Current Password")) html.password_input('cur_password', autocomplete="new-password") forms.section(_("New Password")) html.password_input('password', autocomplete="new-password") forms.section(_("New Password Confirmation")) html.password_input('password2', autocomplete="new-password") if not change_pw and config.user.may('general.edit_profile'): select_language(user) # Let the user configure how he wants to be notified if not rulebased_notifications \ and config.user.may('general.edit_notifications') \ and user.get("notifications_enabled"): forms.section(_("Notifications")) html.help( _("Here you can configure how you want to be notified about host and service problems and " "other monitoring events.")) watolib.get_vs_flexible_notifications().render_input( "notification_method", user.get("notification_method")) if config.user.may('general.edit_user_attributes'): for name, attr in userdb.get_user_attributes(): if attr.user_editable(): vs = attr.valuespec() forms.section(_u(vs.title())) value = user.get(name, vs.default_value()) if not attr.permission() or config.user.may( attr.permission()): vs.render_input("ua_" + name, value) html.help(_u(vs.help())) else: html.write(vs.value_to_text(value)) # Save button forms.end() html.button("_save", _("Save")) html.close_div() html.hidden_fields() html.end_form() html.footer()