def run(self): self._logger.log(VERBOSE, "Initializing application...") environ = dict(create_environ(), REQUEST_URI='') this_html = htmllib.html(Request(environ)) # Currently the htmllib.html constructor enables the timeout by default. This side effect # should really be cleaned up. this_html.disable_request_timeout() with AppContext(DummyApplication(environ, None)), \ RequestContext(this_html): self._initialize_gui_environment() self._logger.log(VERBOSE, "Updating Checkmk configuration...") for step_func, title in self._steps(): self._logger.log(VERBOSE, " + %s..." % title) try: step_func() except Exception: self._logger.log(VERBOSE, " + \"%s\" failed" % title, exc_info=True) if self._arguments.debug: raise self._logger.log(VERBOSE, "Done")
def test_load_group_information_empty(tmp_path): environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): assert groups.load_contact_group_information() == {} assert gui_groups.load_host_group_information() == {} assert gui_groups.load_service_group_information() == {}
def test_get_request( self, monkeypatch: pytest.MonkeyPatch, ) -> None: request = Request({}) request.set_var("site_id", "NO_SITE") request.set_var("to_delete", "['x/y/z.txt', 'abc.ending', '/ä/☃/☕']") request.set_var("config_generation", "123") request.files = werkzeug_datastructures.ImmutableMultiDict({ "sync_archive": werkzeug_datastructures.FileStorage( stream=io.BytesIO(b"some data"), filename="sync_archive", name="sync_archive", ) }) monkeypatch.setattr( activate_changes, "_request", request, ) assert (activate_changes.AutomationReceiveConfigSync().get_request() == activate_changes.ReceiveConfigSyncRequest( site_id=SiteId("NO_SITE"), sync_archive=b"some data", to_delete=["x/y/z.txt", "abc.ending", "/ä/☃/☕"], config_generation=123, ))
def test_write_and_read_host_attributes(tmp_path, attributes, monkeypatch): folder_path = str(tmp_path) # Write/Read operations always require a valid user monkeypatch.setattr(config, "user", config.LoggedInSuperUser()) # Used to write the data write_data_folder = watolib.Folder("testfolder", folder_path=folder_path, parent_folder=None) # Used to read the previously written data read_data_folder = watolib.Folder("testfolder", folder_path=folder_path, parent_folder=None) environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): # Write data # Note: The create_hosts function modifies the attributes dict, adding a meta_data key inplace write_data_folder.create_hosts([("testhost", attributes, [])]) write_folder_hosts = write_data_folder.hosts() assert len(write_folder_hosts) == 1 # Read data back read_folder_hosts = read_data_folder.hosts() assert len(read_folder_hosts) == 1 for _, host in read_folder_hosts.items(): assert host.attributes() == attributes
def test_del_vars(): environ = dict(create_environ(), REQUEST_URI='', QUERY_STRING='foo=foo&_username=foo&_password=bar&bar=bar') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): # First we hit the cached property so we can see that the underlying Request object # actually got replaced later. _ = request.args _ = html.request.args html.request.set_var("foo", "123") html.del_var_from_env("_username") html.del_var_from_env("_password") # Make test independent of dict sorting assert html.request.query_string in [ 'foo=foo&bar=bar', 'bar=bar&foo=foo' ] assert '_password' not in html.request.args assert '_username' not in html.request.args # Check the request local proxied version too. # Make test independent of dict sorting assert request.query_string in ['foo=foo&bar=bar', 'bar=bar&foo=foo'] assert '_password' not in request.args assert '_username' not in request.args assert html.request.var("foo") == "123"
def _SearchContext(self) -> Iterator[None]: _request = Request(create_environ()) with RequestContext( html_obj=html(_request), req=_request, display_options=DisplayOptions(), ), UserContext(self._user_id): yield
def _initialize_gui_environment(self): environ = dict(create_environ(), REQUEST_URI='') current_app.set_current(DummyApplication(environ, None)) html.set_current(htmllib.html(Request(environ), Response(is_secure=False))) cmk.gui.modules.load_all_plugins() cmk.gui.config.load_config() cmk.gui.config.set_super_user()
def module_wide_request_context(): # This one is kind of an hack because some other test-fixtures touch the user object AFTER the # request context has already ended. If we increase our scope this won't matter, but it is of # course wrong. These other fixtures have to be fixed. environ = create_environ() with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): yield
def with_request_context(): environ = create_environ() resp = Response() with AppContext(DummyApplication(environ, None)), \ RequestContext(req=Request(environ), resp=resp, funnel=OutputFunnel(resp), display_options=DisplayOptions()): yield
def _verify_user(environ: WSGIEnvironment, now: datetime) -> RFC7662: verified: List[RFC7662] = [] auth_header = environ.get("HTTP_AUTHORIZATION", "") basic_user = None if auth_header: auth_type, _ = auth_header.split(None, 1) if auth_type == "Bearer": user_id, secret = user_from_bearer_header(auth_header) automation_user = automation_auth(user_id, secret) if automation_user: verified.append(automation_user) else: # GUI user and Automation users are mutually exclusive. Checking only once is less # work for the system. gui_user = gui_user_auth(user_id, secret, now) if gui_user: verified.append(gui_user) elif auth_type == "Basic": # We store this for sanity checking below, once we get a REMOTE_USER key. # If we don't get a REMOTE_USER key, this value will be ignored. basic_user = user_from_basic_header(auth_header) else: raise MKAuthException(f"Unsupported Auth Type: {auth_type}") remote_user = environ.get("REMOTE_USER", "") if remote_user and userdb.user_exists(UserId(remote_user)): if basic_user and basic_user[0] != remote_user: raise MKAuthException("Mismatch in authentication headers.") verified.append(rfc7662_subject(UserId(remote_user), "web_server")) cookie = Request(environ).cookies.get(f"auth_{omd_site()}") if cookie: user_id, session_id, cookie_hash = user_from_cookie(cookie) check_parsed_auth_cookie(user_id, session_id, cookie_hash) verified.append(rfc7662_subject(user_id, "cookie")) if not verified: raise MKAuthException( "You need to be authenticated to use the REST API.") # We pick the first successful authentication method, which means the precedence is the same # as the order in the code. final_candidate = verified[0] user_id = final_candidate["sub"] if not userdb.is_customer_user_allowed_to_login(user_id): raise MKAuthException(f"{user_id} may not log in here.") if userdb.user_locked(user_id): raise MKAuthException(f"{user_id} not authorized.") if change_reason := userdb.need_to_change_pw(user_id, now): raise MKAuthException( f"{user_id} needs to change the password ({change_reason}).")
def with_request_context(): environ = create_environ() resp = Response() with AppContext(session_wsgi_app(debug=False)), RequestContext( req=Request(environ), resp=resp, funnel=OutputFunnel(resp), config_obj=make_config_object(get_default_config()), display_options=DisplayOptions(), ): yield
def run(self): self._logger.log(VERBOSE, "Updating Checkmk configuration...") environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ), Response(is_secure=False))): self._initialize_gui_environment() for step_func, title in self._steps(): self._logger.log(VERBOSE, " + %s..." % title) step_func() self._logger.log(VERBOSE, "Done")
def request_context(environ: Mapping[str, Any]) -> Iterator[None]: this_html = html(Request(environ)) # Currently the htmllib.html constructor enables the timeout by default. This side effect # should really be cleaned up. this_html.disable_request_timeout() with RequestContext( this_html, display_options=DisplayOptions(), prefix_logs_with_url=False, ): yield
def with_request_context(): environ = create_environ() resp = Response() with AppContext(session_wsgi_app(debug=False), stack=app_stack()), RequestContext( req=Request(environ), resp=resp, funnel=OutputFunnel(resp), config_obj=make_config_object(get_default_config()), user=LoggedInNobody(), display_options=DisplayOptions(), stack=request_stack(), url_filter=PrependURLFilter(), ): yield
def request_context(environ: Mapping[str, Any]) -> Iterator[None]: req = Request(environ) resp = Response(mimetype="text/html") funnel = OutputFunnel(resp) with RequestContext( req=req, resp=resp, funnel=funnel, html_obj=html(req, resp, funnel, output_format="html"), display_options=DisplayOptions(), theme=Theme(), prefix_logs_with_url=False, ): yield
def make_request_context( environ: Optional[Mapping[str, Any]] = None) -> RequestContext: req = Request( dict(create_environ(), REQUEST_URI="") if environ is None else environ) resp = Response(mimetype="text/html") funnel = OutputFunnel(resp) return RequestContext( req=req, resp=resp, funnel=funnel, config_obj=make_config_object(get_default_config()), user=LoggedInNobody(), html_obj=html(req, resp, funnel, output_format="html"), display_options=DisplayOptions(), timeout_manager=TimeoutManager(), theme=Theme(), prefix_logs_with_url=False, )
def _SearchContext(self) -> Iterator[None]: _request = Request(create_environ()) _response = Response() _funnel = OutputFunnel(_response) _theme = Theme() _theme.from_config(config.ui_theme, config.theme_choices()) with RequestContext( req=_request, resp=_response, funnel=_funnel, html_obj=html(_request, _response, _funnel, output_format="html"), display_options=DisplayOptions(), theme=_theme, ), UserContext(self._user_id): yield
def run(self): self._logger.log(VERBOSE, "Initializing application...") environ = dict(create_environ(), REQUEST_URI='') this_html = htmllib.html(Request(environ), Response(is_secure=False)) # Currently the htmllib.html constructor enables the timeout by default. This side effect # should really be cleaned up. this_html.disable_request_timeout() with AppContext(DummyApplication(environ, None)), \ RequestContext(this_html): self._initialize_gui_environment() self._logger.log(VERBOSE, "Updating Checkmk configuration...") for step_func, title in self._steps(): self._logger.log(VERBOSE, " + %s..." % title) step_func() self._logger.log(VERBOSE, "Done")
def test_generate_snapshot(edition_short, snapshot_manager_class, monkeypatch, tmp_path, with_user): user_id = with_user[0] monkeypatch.setattr(cmk_version, "edition_short", lambda: edition_short) environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): login.login(user_id) _create_test_sync_config(monkeypatch) snapshot_settings = _create_sync_snapshot(snapshot_manager_class, monkeypatch, tmp_path) # And now check the resulting snapshot contents unpack_dir = tmp_path / "snapshot_unpack" with tarfile.open(snapshot_settings.snapshot_path, "r") as t: t.extractall(str(unpack_dir)) expected_subtars = [ "auth.secret.tar", "auth.serials.tar", "check_mk.tar", "diskspace.tar", "htpasswd.tar", "mkeventd_mkp.tar", "mkeventd.tar", "multisite.tar", "sitespecific.tar", "usersettings.tar", ] if is_enterprise_repo(): expected_subtars += [ "dcd.tar", "mknotify.tar", ] if not cmk_version.is_raw_edition(): expected_subtars.append("liveproxyd.tar") assert sorted(f.name for f in unpack_dir.iterdir()) == sorted(expected_subtars) expected_files = { 'mkeventd_mkp.tar': [], 'multisite.tar': ["global.mk", "users.mk"], 'usersettings.tar': [user_id], 'mkeventd.tar': [], 'check_mk.tar': ["hosts.mk", "contacts.mk"], 'htpasswd.tar': ["htpasswd"], 'liveproxyd.tar': [], 'sitespecific.tar': ['sitespecific.mk'], 'auth.secret.tar': [], 'dcd.tar': [], 'auth.serials.tar': ["auth.serials"], 'mknotify.tar': [], 'diskspace.tar': [], } # TODO: Shouldn't we clean up these subtle differences? if cmk_version.is_managed_edition(): expected_files.update({ "mkeventd.tar": ["rules.mk"], 'check_mk.tar': ["groups.mk", "contacts.mk"], 'multisite.tar': [ "bi.mk", "customers.mk", "global.mk", "groups.mk", "user_connections.mk", "users.mk", ], }) if not cmk_version.is_raw_edition(): expected_files['liveproxyd.tar'] = [] # And now check the subtar contents for subtar in unpack_dir.iterdir(): subtar_unpack_dir = unpack_dir / subtar.stem subtar_unpack_dir.mkdir(parents=True, exist_ok=True) with tarfile.open(str(subtar), "r") as s: s.extractall(str(subtar_unpack_dir)) files = sorted(str(f.relative_to(subtar_unpack_dir)) for f in subtar_unpack_dir.iterdir()) assert sorted(expected_files[subtar.name]) == files, \ "Subtar %s has wrong files" % subtar.name
def __init__(self): self._user_id = user.ident self._request = Request(create_environ()) from cmk.gui.wato.pages.hosts import ModeEditHost self._mode_edit_host = ModeEditHost
def register_builtin_html(): """This fixture registers a global htmllib.html() instance just like the regular GUI""" environ = create_environ() with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): yield
def test_load_group_information(tmp_path): with open(cmk.utils.paths.check_mk_config_dir + "/wato/groups.mk", "w") as f: f.write("""# encoding: utf-8 if type(define_contactgroups) != dict: define_contactgroups = {} define_contactgroups.update({'all': u'Everything'}) if type(define_hostgroups) != dict: define_hostgroups = {} define_hostgroups.update({'all_hosts': u'All hosts :-)'}) if type(define_servicegroups) != dict: define_servicegroups = {} define_servicegroups.update({'all_services': u'All särvices'}) """) with open(cmk.utils.paths.default_config_dir + "/multisite.d/wato/groups.mk", "w") as f: f.write("""# encoding: utf-8 multisite_hostgroups = { "all_hosts": { "ding": "dong", }, } multisite_servicegroups = { "all_services": { "d1ng": "dong", }, } multisite_contactgroups = { "all": { "d!ng": "dong", }, } """) environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): assert groups.load_group_information() == { 'contact': { 'all': { 'alias': u'Everything', "d!ng": "dong", } }, 'host': { 'all_hosts': { 'alias': u'All hosts :-)', "ding": "dong", } }, 'service': { 'all_services': { 'alias': u'All s\xe4rvices', "d1ng": "dong", } }, } assert groups.load_contact_group_information() == { 'all': { 'alias': u'Everything', "d!ng": "dong", } } assert gui_groups.load_host_group_information() == { 'all_hosts': { 'alias': u'All hosts :-)', "ding": "dong", } } assert gui_groups.load_service_group_information() == { 'all_services': { 'alias': u'All s\xe4rvices', "d1ng": "dong", } }
def test_apply_sync_snapshot(edition_short, snapshot_manager_class, monkeypatch, tmp_path, with_user): user_id = with_user[0] monkeypatch.setattr(cmk_version, "edition_short", lambda: edition_short) environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ))): login.login(user_id) _create_test_sync_config(monkeypatch) snapshot_settings = _create_sync_snapshot(snapshot_manager_class, monkeypatch, tmp_path) # Change unpack target directory from "unit test site" paths to a test specific path unpack_dir = tmp_path / "snapshot_unpack" components = [ activate_changes._replace_omd_root(str(unpack_dir), p) for p in activate_changes.get_replication_paths() ] with open(snapshot_settings.snapshot_path, "rb") as f: activate_changes.apply_sync_snapshot("unit_remote_1", f.read(), components) expected_paths = [ 'etc', 'var', 'etc/check_mk', 'etc/check_mk/conf.d', 'etc/check_mk/mkeventd.d', 'etc/check_mk/multisite.d', 'etc/check_mk/conf.d/wato', 'etc/check_mk/conf.d/wato/hosts.mk', 'etc/check_mk/conf.d/wato/contacts.mk', 'etc/check_mk/mkeventd.d/mkp', 'etc/check_mk/mkeventd.d/wato', 'etc/check_mk/mkeventd.d/mkp/rule_packs', 'etc/check_mk/multisite.d/wato', 'etc/check_mk/multisite.d/wato/global.mk', 'var/check_mk', 'var/check_mk/web', "etc/htpasswd", "etc/auth.serials", "etc/check_mk/multisite.d/wato/users.mk", six.ensure_str('var/check_mk/web/%s' % user_id), six.ensure_str('var/check_mk/web/%s/cached_profile.mk' % user_id), six.ensure_str('var/check_mk/web/%s/enforce_pw_change.mk' % user_id), six.ensure_str('var/check_mk/web/%s/last_pw_change.mk' % user_id), six.ensure_str('var/check_mk/web/%s/num_failed_logins.mk' % user_id), six.ensure_str('var/check_mk/web/%s/serial.mk' % user_id), ] # The paths are registered once the enterprise plugins are available, independent of the # cmk_version.edition_short() value. if is_enterprise_repo(): expected_paths += [ 'etc/check_mk/dcd.d', 'etc/check_mk/mknotifyd.d', 'etc/check_mk/mknotifyd.d/wato', 'etc/check_mk/dcd.d/wato', ] # TODO: Shouldn't we clean up these subtle differences? if cmk_version.is_managed_edition(): expected_paths += [ "etc/check_mk/conf.d/wato/groups.mk", "etc/check_mk/mkeventd.d/wato/rules.mk", "etc/check_mk/multisite.d/wato/bi.mk", "etc/check_mk/multisite.d/wato/customers.mk", "etc/check_mk/multisite.d/wato/groups.mk", "etc/check_mk/multisite.d/wato/user_connections.mk", #"etc/check_mk/conf.d/wato/sitespecific.mk", #"etc/check_mk/dcd.d/wato/distributed.mk", #"etc/check_mk/dcd.d/wato/sitespecific.mk", #"etc/check_mk/mkeventd.d/wato/sitespecific.mk", #"etc/check_mk/mknotifyd.d/wato/sitespecific.mk", #"etc/check_mk/multisite.d/wato/sitespecific.mk", ] expected_paths.remove("etc/check_mk/conf.d/wato/hosts.mk") if not cmk_version.is_raw_edition(): expected_paths += [ 'etc/check_mk/liveproxyd.d', 'etc/check_mk/liveproxyd.d/wato', #'etc/check_mk/multisite.d/wato/ca-certificates_sitespecific.mk', ] paths = [str(p.relative_to(unpack_dir)) for p in unpack_dir.glob("**/*")] assert sorted(paths) == sorted(expected_paths)
def register_builtin_html(): """This fixture registers a global htmllib.html() instance just like the regular GUI""" environ = dict(create_environ(), REQUEST_URI='') with AppContext(DummyApplication(environ, None)), \ RequestContext(htmllib.html(Request(environ), Response(is_secure=False))): yield
def _wsgi_app(self, environ: WSGIEnvironment, start_response: StartResponse) -> WSGIResponse: urls = self.url_map.bind_to_environ(environ) endpoint: Optional[Endpoint] = None try: result: Tuple[str, Mapping[str, Any]] = urls.match(return_rule=False) endpoint_ident, matched_path_args = result # pylint: disable=unpacking-non-sequence wsgi_app = self.endpoints[endpoint_ident] if isinstance(wsgi_app, Authenticate): endpoint = wsgi_app.endpoint # Remove _path again (see Submount above), so the validators don't go crazy. path_args = { key: value for key, value in matched_path_args.items() if key != "_path" } # This is an implicit dependency, as we only know the args at runtime, but the # function at setup-time. environ[ARGS_KEY] = path_args req = Request(environ) resp = Response() with AppContext(self, stack=app_stack()), RequestContext( req=req, resp=resp, funnel=OutputFunnel(resp), config_obj=config.make_config_object( config.get_default_config()), endpoint=endpoint, user=LoggedInNobody(), display_options=DisplayOptions(), stack=request_stack(), url_filter=PrependURLFilter(), ), cmk.utils.store.cleanup_locks(), sites.cleanup_connections(): config.initialize() load_dynamic_permissions() return wsgi_app(environ, start_response) except ProblemException as exc: return exc(environ, start_response) except HTTPException as exc: # We don't want to log explicit HTTPExceptions as these are intentional. assert isinstance(exc.code, int) return problem( status=exc.code, title=http.client.responses[exc.code], detail=str(exc), )(environ, start_response) except MKException as exc: if self.debug: raise return problem( status=EXCEPTION_STATUS.get(type(exc), 500), title="An exception occurred.", detail=str(exc), )(environ, start_response) except Exception as exc: crash = APICrashReport.from_exception() crash_reporting.CrashReportStore().save(crash) logger.exception("Unhandled exception (Crash-ID: %s)", crash.ident_to_text()) if self.debug: raise request = Request(environ) site = config.omd_site() query_string = urllib.parse.urlencode([ ("crash_id", (crash.ident_to_text())), ("site", site), ]) crash_url = f"{request.host_url}{site}/check_mk/crash.py?{query_string}" crash_details = { "crash_id": (crash.ident_to_text()), "crash_report": { "href": crash_url, "method": "get", "rel": "cmk/crash-report", "type": "text/html", }, } if user.may("general.see_crash_reports"): crash_details["stack_trace"] = traceback.format_exc().split( "\n") return problem( status=500, title=http.client.responses[500], detail=str(exc), ext=crash_details, )(environ, start_response)
def __init__(self): self._user_id = user.ident self._request = Request(create_environ())