def _UserContext(user_obj: LoggedInUser) -> Iterator[None]: """Managing authenticated user context After the user has been authenticated, initialize the global user object.""" old_user = request_local_attr().user try: request_local_attr().user = user_obj yield finally: request_local_attr().user = old_user
def _translation() -> Optional[Translation]: try: return request_local_attr().translation except RuntimeError: # TODO: Once we cleaned up all wrong _() to _l(), we can clean this up pass return None
def begin_form( self, name: str, action: Optional[str] = None, method: str = "GET", onsubmit: Optional[str] = None, add_transid: bool = True, ) -> None: self.form_name = name self.form_vars = [] self.form_has_submit_button = False if action is None: action = requested_file_name(self.request) + ".py" self.open_form( id_="form_%s" % name, name=name, class_=name, action=action, method=method, onsubmit=onsubmit, enctype="multipart/form-data" if method.lower() == "post" else None, ) session = request_local_attr("session") if hasattr(session, "session_info"): self.hidden_field("csrf_token", session.session_info.csrf_token) self.hidden_field("filled_in", name, add_var=True) if add_transid: self.hidden_field( "_transid", str(transactions.get()), add_var=True, )
def _set_js_csrf_token(self) -> None: session = request_local_attr("session") # session is LocalProxy, only on access it is None, so we cannot test on 'is None' if not hasattr(session, "session_info"): return self.javascript( "var global_csrf_token = %s;" % (json.dumps(session.session_info.csrf_token)) )
def localize(lang: Optional[str]) -> None: if lang is None: unlocalize() return gettext_translation = _init_language(lang) if not gettext_translation: unlocalize() return request_local_attr().translation = Translation( translation=gettext_translation, name=lang)
def test_get_start_url_user_config( monkeypatch: MonkeyPatch, request_context: RequestContextFixture) -> None: monkeypatch.setattr(active_config, "start_url", "bla.py") class MockUser: @property def start_url(self) -> str: return "user_url.py" local = request_local_attr() monkeypatch.setattr(local, "user", MockUser()) assert cmk.gui.main._get_start_url() == "user_url.py"
def load_config() -> None: # Set default values for all user-changable configuration settings raw_config = get_default_config() # Initialize sites with default site configuration. Need to do it here to # override possibly deleted sites raw_config["sites"] = default_single_site_configuration() # Load assorted experimental parameters if any experimental_config = cmk.utils.paths.make_experimental_config_file() if experimental_config.exists(): _load_config_file_to(str(experimental_config), raw_config) # First load main file _load_config_file_to(cmk.utils.paths.default_config_dir + "/multisite.mk", raw_config) # Load also recursively all files below multisite.d conf_dir = cmk.utils.paths.default_config_dir + "/multisite.d" filelist = [] if os.path.isdir(conf_dir): for root, _directories, files in os.walk(conf_dir): for filename in files: if filename.endswith(".mk"): filelist.append(root + "/" + filename) filelist.sort() for p in filelist: _load_config_file_to(p, raw_config) raw_config["sites"] = prepare_raw_site_config(raw_config["sites"]) raw_config["tags"] = cmk.utils.tags.get_effective_tag_config(raw_config["wato_tags"]) # TODO: Temporary local hack to transform the values to the correct type. This needs # to be done in make_config_object() in the next step. if "agent_signature_keys" in raw_config: raw_config["agent_signature_keys"] = { key_id: Key.parse_obj(raw_key) for key_id, raw_key in raw_config["agent_signature_keys"].items() } # Make sure, builtin roles are present, even if not modified and saved with WATO. for br in builtin_role_ids: raw_config["roles"].setdefault(br, {}) request_local_attr().config = make_config_object(raw_config) execute_post_config_load_hooks()
self._response_stack: List[Response] = [response] def write(self, data: bytes) -> None: self._response_stack[-1].stream.write(data) @contextmanager def plugged(self) -> Iterator[None]: self._response_stack.append(Response()) try: yield finally: response = self._response_stack.pop() # Rest of popped response is written to now topmost request. # TODO: Investigate call sites whether or not this is a used feature self.write(response.get_data()) def _is_plugged(self) -> bool: return len(self._response_stack) > 1 def drain(self) -> str: """Return the content of the topmost response object""" if not self._is_plugged(): return "" text = self._response_stack.pop().get_data(as_text=True) self._response_stack.append(Response()) return text output_funnel: OutputFunnel = request_local_attr("output_funnel")
import cmk.gui.permissions as permissions import cmk.gui.site_config as site_config from cmk.gui.config import active_config, builtin_role_ids from cmk.gui.ctx_stack import request_local_attr from cmk.gui.exceptions import MKAuthException from cmk.gui.http import request from cmk.gui.i18n import _ from cmk.gui.utils.roles import may_with_roles, roles_of_user from cmk.gui.utils.transaction_manager import TransactionManager if TYPE_CHECKING: # Cyclic import! from cmk.gui.plugins.openapi.restful_objects import Endpoint endpoint: Endpoint = request_local_attr("endpoint") class LoggedInUser: """Manage the currently logged in user This objects intention is currently only to handle the currently logged in user after authentication. """ def __init__(self, user_id: Optional[str]) -> None: self.id = UserId(user_id) if user_id else None self.transactions = TransactionManager(request, self) self.confdir = _confdir_for_user_id(self.id) self.role_ids = self._gather_roles(self.id) baserole_ids = _baserole_ids_from_role_ids(self.role_ids)
if "admin" in baserole_ids: return "admin" if "user" in baserole_ids: return "user" return "guest" def _initial_permission_cache(user_id: Optional[UserId]) -> Dict[str, bool]: if user_id is None: return {} # Prepare cache of already computed permissions # Make sure, admin can restore permissions in any case! if user_id in active_config.admin_users: return { "general.use": True, # use Multisite "wato.use": True, # enter WATO "wato.edit": True, # make changes in WATO... "wato.users": True, # ... with access to user management } return {} def save_user_file(name: str, data: Any, user_id: UserId) -> None: path = cmk.utils.paths.profile_dir.joinpath(user_id, name + ".mk") store.mkdir(path.parent) store.save_object_to_file(path, data) user: LoggedInUser = request_local_attr("user")
client. It is possible to disable the applications request timeout (temoporarily) or totally for specific calls, but the timeout to the client will always be applied by the system webserver. So the client will always get a error page while the site apache continues processing the request (until the first try to write anything to the client) which will result in an exception. """ def enable_timeout(self, duration: int) -> None: def handle_request_timeout(signum: int, frame: Optional[FrameType]) -> None: raise RequestTimeout( _( "Your request timed out after %d seconds. This issue may be " "related to a local configuration problem or a request which works " "with a too large number of objects. But if you think this " "issue is a bug, please send a crash report." ) % duration ) signal.signal(signal.SIGALRM, handle_request_timeout) signal.alarm(duration) def disable_timeout(self) -> None: signal.alarm(0) timeout_manager: TimeoutManager = request_local_attr("timeout_manager")
def check_transaction(self) -> bool: """called by page functions in order to check, if this was a reload or the original form submission. Increases the transid of the user, if the latter was the case. There are three return codes: True: -> positive confirmation by the user False: -> not yet confirmed, question is being shown None: -> a browser reload or a negative confirmation """ if self.transaction_valid(): transid = self._request.var("_transid") if transid and transid != "-1": self._invalidate(transid) return True return False def _invalidate(self, used_id: str) -> None: """Remove the used transid from the list of valid ones""" valid_ids = self._user.transids(lock=True) try: valid_ids.remove(used_id) except ValueError: return self._user.save_transids(valid_ids) transactions: TransactionManager = request_local_attr("transactions")
page. """ from typing import Dict, Iterator, Mapping, Optional from cmk.gui.ctx_stack import request_local_attr from cmk.gui.exceptions import MKUserError class UserErrors(Mapping[Optional[str], str]): def __init__(self) -> None: self._errors: Dict[Optional[str], str] = {} def add(self, error: MKUserError) -> None: self._errors[error.varname] = str(error) def __bool__(self) -> bool: return bool(self._errors) def __getitem__(self, key: Optional[str]) -> str: return self._errors.__getitem__(key) def __iter__(self) -> Iterator[Optional[str]]: return self._errors.__iter__() def __len__(self) -> int: return len(self._errors) user_errors: UserErrors = request_local_attr("user_errors")
(json_request, e)) for key, val in self.itervars(): if key not in ["request", "output_format"] + exclude_vars: request_[key] = val return request_ class Response(werkzeug.Response): # NOTE: Currently we rely on a *relative* Location header in redirects! autocorrect_location_header = False default_mimetype = "text/html" def set_http_cookie(self, key: str, value: str, *, secure: bool) -> None: super().set_cookie(key, value, secure=secure, httponly=True, samesite="Lax") def set_content_type(self, mime_type: str) -> None: self.headers["Content-type"] = get_content_type( mime_type, self.charset) # From request context request: Request = request_local_attr("request") response: Response = request_local_attr("response")
The `decorators` module. The other modules will be called from there. The modules: * The `request_schemas` and `response_schemas` modules are special, as they only contain abstract models of the request and response formats and will be used in validation (currently only response). * The `specification` module is the entry point for the OpenAPI spec generation code and contains mostly boilerplate code and some useful parameter definitions. * The `constructors` module contains helpers which generate parts of the nested JSON struct that is specified by the Restful Objects specification. * The `code_examples` module contains a helper which renders Jinja2 templates with source code examples. These are put into the spec by the decorator. The examples are specific to the Redoc documentation generator library. """ from cmk.gui.ctx_stack import request_local_attr from cmk.gui.plugins.openapi.restful_objects.decorators import Endpoint from cmk.gui.plugins.openapi.restful_objects.specification import SPEC __all__ = ["Endpoint", "SPEC", "endpoint"] endpoint: Endpoint = request_local_attr("endpoint")
Path(cmk.utils.paths.web_dir), cmk.utils.paths.local_web_dir ]: if not base_dir.exists(): continue theme_base_dir = base_dir / "htdocs" / "themes" if not theme_base_dir.exists(): continue for theme_dir in theme_base_dir.iterdir(): meta_file = theme_dir / "theme.json" if not meta_file.exists(): continue try: theme_meta = json.loads( meta_file.open(encoding="utf-8").read()) except ValueError: # Ignore broken meta files and show the directory name as title theme_meta = { "title": theme_dir.name, } assert isinstance(theme_meta["title"], str) themes[theme_dir.name] = theme_meta["title"] return sorted(themes.items()) theme: Theme = request_local_attr("theme")
_check_auth_cookie(cookie_name) except MKAuthException: # Suppress cookie validation errors from other sites cookies auth_logger.debug("Exception while checking cookie %s: %s" % (cookie_name, traceback.format_exc())) except Exception: auth_logger.debug("Exception while checking cookie %s: %s" % (cookie_name, traceback.format_exc())) def set_auth_type(_auth_type: AuthType) -> None: request_local_attr().auth_type = _auth_type auth_type: Union[AuthType, LocalProxy] = LocalProxy( lambda: request_local_attr().auth_type) @page_registry.register_page("login") class LoginPage(Page): def __init__(self) -> None: super().__init__() self._no_html_output = False def set_no_html_output(self, no_html_output: bool) -> None: self._no_html_output = no_html_output def page(self) -> None: # Initialize the cmk.gui.i18n for the login dialog. This might be # overridden later after user login cmk.gui.i18n.localize(
def set_auth_type(_auth_type: AuthType) -> None: request_local_attr().auth_type = _auth_type
drop_handler="function(index){return cmk.element_dragging.url_drop_handler(%s, index);})" % json.dumps(base_url), ) ) def element_dragger_js( self, dragging_tag: str, drop_handler: str, handler_args: Mapping[str, Any] ) -> None: self.write_html( HTMLGenerator.render_element_dragger( dragging_tag, drop_handler="function(new_index){return %s(%s, new_index);})" % (drop_handler, json.dumps(handler_args)), ) ) # Currently only tested with tables. But with some small changes it may work with other # structures too. @staticmethod def render_element_dragger(dragging_tag: str, drop_handler: str) -> HTML: return HTMLWriter.render_a( HTMLGenerator.render_icon("drag", _("Move this entry")), href="javascript:void(0)", class_=["element_dragger"], onmousedown="cmk.element_dragging.start(event, this, %s, %s" % (json.dumps(dragging_tag.upper()), drop_handler), ) html: HTMLGenerator = request_local_attr("html")
###################################################################### # TODO: This should live somewhere else... class PrependURLFilter(logging.Filter): def filter(self, record): if record.levelno >= logging.ERROR: record.msg = "%s %s" % (request.requested_url, record.msg) return True # From app context current_app = LocalProxy(partial(_lookup_app_object, "app")) g: Any = LocalProxy(partial(_lookup_app_object, "g")) # NOTE: All types FOO below are actually a Union[Foo, LocalProxy], but # LocalProxy is meant as a transparent proxy, so we leave it out to de-confuse # mypy. LocalProxy uses a lot of reflection magic, which can't be understood by # tools in general. # From request context request: http.Request = request_local_attr("request") response: http.Response = request_local_attr("response") output_funnel: OutputFunnel = request_local_attr("output_funnel") active_config: Config = request_local_attr("config") endpoint: Endpoint = request_local_attr("endpoint") user_errors: UserErrors = request_local_attr("user_errors") html: htmllib.html = request_local_attr("html") timeout_manager: TimeoutManager = request_local_attr("timeout_manager") theme: Theme = request_local_attr("theme") display_options: DisplayOptions = request_local_attr("display_options")
@dataclass class Config(CREConfig, CEEConfig, CMEConfig): """Holds the loaded configuration during GUI processing The loaded configuration is then accessible through `from cmk.gui.globals import config`. For builtin config variables type checking and code completion works. This class is extended by `load_config` to support custom config variables which may be introduced by 3rd party extensions. For these variables we don't have the features mentioned above. But that's fine for now. """ tags: cmk.utils.tags.TagConfig = cmk.utils.tags.TagConfig() active_config: Config = request_local_attr("config") # . # .--Functions-----------------------------------------------------------. # | _____ _ _ | # | | ___| _ _ __ ___| |_(_) ___ _ __ ___ | # | | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __| | # | | _|| |_| | | | | (__| |_| | (_) | | | \__ \ | # | |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ | # | | # +----------------------------------------------------------------------+ # | Helper functions for config parsing, login, etc. | # '----------------------------------------------------------------------'
# links). These links need to know about the provided display_option parameter. The links # could use "display_options.options" but this contains the implicit options which should # not be added to the URLs. So the real parameters need to be preserved for this case. self.title_options = request.get_ascii_input("display_options") # If display option 'M' is set, then all links are targetet to the 'main' # frame. Also the display options are removed since the view in the main # frame should be displayed in standard mode. if self.disabled(self.M): html.link_target = "main" request.del_var("display_options") # If all display_options are upper case assume all not given values default # to lower-case. Vice versa when all display_options are lower case. # When the display_options are mixed case assume all unset options to be enabled def _merge_with_defaults(self, opts: str) -> str: do_defaults = self.all_off() if opts.isupper() else self.all_on() for c in do_defaults: if c.lower() not in opts.lower(): opts += c return opts def enabled(self, opt: str) -> bool: return opt in self.options def disabled(self, opt: str) -> bool: return opt not in self.options display_options: DisplayOptions = request_local_attr("display_options")
def unlocalize() -> None: request_local_attr().translation = None