Exemple #1
0
def _execute_action_no_lock(api_call, request_object):
    if cmk.gui.watolib.read_only.is_enabled() and not cmk.gui.watolib.read_only.may_override():
        raise MKUserError(None, cmk.gui.watolib.read_only.message())

    # We assume something will be modified and increase the config generation
    update_config_generation()

    return {
        "result_code": 0,
        "result": api_call["handler"](request_object),
    }
Exemple #2
0
def test_get_current_config_generation():
    assert activate_changes._get_current_config_generation() == 0
    activate_changes.update_config_generation()
    assert activate_changes._get_current_config_generation() == 1
    activate_changes.update_config_generation()
    activate_changes.update_config_generation()
    assert activate_changes._get_current_config_generation() == 3
Exemple #3
0
        def _validating_wrapper(param):
            # TODO: Better error messages, pointing to the location where variables are missing

            def _format_fields(_messages: Union[List, Dict]) -> str:
                if isinstance(_messages, list):
                    return ", ".join(_messages)
                if isinstance(_messages, dict):
                    return ", ".join(_messages.keys())
                return ""

            def _problem(exc_, status_code=400):
                if isinstance(exc_.messages, dict):
                    messages = exc_.messages
                else:
                    messages = {"exc": exc_.messages}
                return problem(
                    status=status_code,
                    title=http.client.responses[status_code],
                    detail=
                    f"These fields have problems: {_format_fields(exc_.messages)}",
                    ext={"fields": messages},
                )

            if self.method in ("post", "put") and request.get_data(cache=True):
                try:
                    self._is_expected_content_type(request.content_type)
                except ValueError as exc:
                    return problem(
                        status=415,
                        detail=str(exc),
                        title="Content type not valid for this endpoint.",
                    )

            try:
                if path_schema:
                    param.update(path_schema().load(param))
            except ValidationError as exc:
                return _problem(exc, status_code=404)

            try:
                if query_schema:
                    param.update(query_schema().load(
                        _from_multi_dict(request.args)))

                if header_schema:
                    param.update(header_schema().load(request.headers))

                if request_schema:
                    # Try to decode only when there is data. Decoding an empty string will fail.
                    if request.get_data(cache=True):
                        json_data = request.json or {}
                    else:
                        json_data = {}
                    param["body"] = request_schema().load(json_data)
            except ValidationError as exc:
                return _problem(exc, status_code=400)

            # make pylint happy
            assert callable(self.func)

            if self.tag_group == "Setup" and not config.wato_enabled:
                return problem(
                    status=403,
                    title="Forbidden: WATO is disabled",
                    detail="This endpoint is currently disabled via the "
                    "'Disable remote configuration' option in 'Distributed Monitoring'. "
                    "You may be able to query the central site.",
                )

            if not self.skip_locking and self.method != "get":
                with store.lock_checkmk_configuration():
                    response = self.func(param)
            else:
                response = self.func(param)

            response.freeze()

            if self.output_empty and response.status_code < 400 and response.data:
                return problem(
                    status=500,
                    title="Unexpected data was sent.",
                    detail=(f"Endpoint {self.operation_id}\n"
                            "This is a bug, please report."),
                    ext={"data_sent": str(response.data)},
                )

            if self.output_empty:
                response.content_type = None

            if response.status_code not in self._expected_status_codes:
                return problem(
                    status=500,
                    title=
                    f"Unexpected status code returned: {response.status_code}",
                    detail=(f"Endpoint {self.operation_id}\n"
                            "This is a bug, please report."),
                    ext={"codes": self._expected_status_codes},
                )

            # We assume something has been modified and increase the config generation ID
            # by one. This is necessary to ensure a warning in the "Activate Changes" GUI
            # about there being new changes to activate can be given to the user.
            if request.method != "get" and response.status_code < 300:
                update_config_generation()

                # We assume no configuration change on GET and no configuration change on
                # non-ok responses.
                if config.wato_use_git:
                    do_git_commit()

            # We assume something has been modified and increase the config generation ID
            # by one. This is necessary to ensure a warning in the "Activate Changes" GUI
            # about there being new changes to activate can be given to the user.
            if request.method != "get" and response.status_code < 300:
                update_config_generation()

                # We assume no configuration change on GET and no configuration change on
                # non-ok responses.
                if config.wato_use_git:
                    do_git_commit()

            # We assume something has been modified and increase the config generation ID
            # by one. This is necessary to ensure a warning in the "Activate Changes" GUI
            # about there being new changes to activate can be given to the user.
            if request.method != "get" and response.status_code < 300:
                update_config_generation()

                # We assume no configuration change on GET and no configuration change on
                # non-ok responses.
                if config.wato_use_git:
                    do_git_commit()

            if (self.content_type == "application/json"
                    and response.status_code < 300 and response_schema
                    and response.data):
                try:
                    data = json.loads(response.data.decode("utf-8"))
                except json.decoder.JSONDecodeError as exc:
                    return problem(
                        status=500,
                        title="Server was about to send invalid JSON data.",
                        detail="This is an error of the implementation.",
                        ext={
                            "errors": str(exc),
                            "orig": response.data,
                        },
                    )
                try:
                    outbound = response_schema().dump(data)
                except ValidationError as exc:
                    return problem(
                        status=500,
                        title="Server was about to send an invalid response.",
                        detail="This is an error of the implementation.",
                        ext={
                            "errors": exc.messages,
                            "orig": data,
                        },
                    )

                if self.convert_response:
                    response.set_data(json.dumps(outbound))

            response.freeze()
            return response
Exemple #4
0
def _wato_page_handler(current_mode: str,
                       mode_permissions: Optional[List[PermissionName]],
                       mode_class: Type[WatoMode]) -> None:
    try:
        init_wato_datastructures(with_wato_lock=not html.is_transaction())
    except Exception:
        # Snapshot must work in any case
        if current_mode == 'snapshot':
            pass
        else:
            raise

    # Check general permission for this mode
    if mode_permissions is not None and not config.user.may("wato.seeall"):
        _ensure_mode_permissions(mode_permissions)

    mode = mode_class()

    # Do actions (might switch mode)
    if html.is_transaction():
        try:
            config.user.need_permission("wato.edit")

            # Even if the user has seen this mode because auf "seeall",
            # he needs an explicit access permission for doing changes:
            if config.user.may("wato.seeall"):
                if mode_permissions:
                    _ensure_mode_permissions(mode_permissions)

            if cmk.gui.watolib.read_only.is_enabled(
            ) and not cmk.gui.watolib.read_only.may_override():
                raise MKUserError(None, cmk.gui.watolib.read_only.message())

            result = mode.action()
            if isinstance(result, (tuple, str, bool)):
                raise MKGeneralException(
                    f"WatoMode \"{current_mode}\" returns unsupported return value: {result!r}"
                )

            # We assume something has been modified and increase the config generation ID by one.
            update_config_generation()

            if config.wato_use_git:
                do_git_commit()

            # Handle two cases:
            # a) Don't render the page content after action
            #    (a confirm dialog is displayed by the action, or a non-HTML content was sent)
            # b) Redirect to another page
            if isinstance(result, FinalizeRequest):
                raise result

        except MKUserError as e:
            html.add_user_error(e.varname, str(e))

        except MKAuthException as e:
            reason = e.args[0]
            html.add_user_error(None, reason)

    breadcrumb = make_main_menu_breadcrumb(
        mode.main_menu()) + mode.breadcrumb()
    page_menu = mode.page_menu(breadcrumb)
    wato_html_head(title=mode.title(),
                   breadcrumb=breadcrumb,
                   page_menu=page_menu,
                   show_body_start=display_options.enabled(display_options.H),
                   show_top_heading=display_options.enabled(display_options.T))

    if not html.is_transaction() or (cmk.gui.watolib.read_only.is_enabled() and
                                     cmk.gui.watolib.read_only.may_override()):
        _show_read_only_warning()

    # Show outcome of failed action on this page
    if html.has_user_errors():
        html.show_user_errors()

    # Show outcome of previous page (that redirected to this one)
    for message in get_flashed_messages():
        html.show_message(message)

    # Show content
    mode.handle_page()

    if is_sidebar_reload_needed():
        html.reload_whole_page()

    wato_html_footer(show_body_end=display_options.enabled(display_options.H))
Exemple #5
0
def _wato_page_handler(current_mode: str,
                       mode_permissions: List[PermissionName],
                       mode_class: Type[WatoMode]) -> None:
    try:
        init_wato_datastructures(with_wato_lock=not html.is_transaction())
    except Exception:
        # Snapshot must work in any case
        if current_mode == 'snapshot':
            pass
        else:
            raise

    # Check general permission for this mode
    if mode_permissions is not None and not config.user.may("wato.seeall"):
        _ensure_mode_permissions(mode_permissions)

    mode = mode_class()

    # Do actions (might switch mode)
    action_message: Optional[str] = None
    if html.is_transaction():
        try:
            config.user.need_permission("wato.edit")

            # Even if the user has seen this mode because auf "seeall",
            # he needs an explicit access permission for doing changes:
            if config.user.may("wato.seeall"):
                if mode_permissions:
                    _ensure_mode_permissions(mode_permissions)

            if cmk.gui.watolib.read_only.is_enabled(
            ) and not cmk.gui.watolib.read_only.may_override():
                raise MKUserError(None, cmk.gui.watolib.read_only.message())

            result = mode.action()
            if isinstance(result, tuple):
                newmode, action_message = result
            else:
                newmode = result

            # We assume something has been modified and increase the config generation ID by one.
            update_config_generation()

            # If newmode is False, then we shall immediately abort.
            # This is e.g. the case, if the page outputted non-HTML
            # data, such as a tarball (in the export function). We must
            # be sure not to output *any* further data in that case.
            if newmode is False:
                return

            # if newmode is not None, then the mode has been changed
            if newmode is not None:
                assert not isinstance(newmode, bool)
                if newmode == "":  # no further information: configuration dialog, etc.
                    if action_message:
                        html.show_message(action_message)
                        wato_html_footer()
                    return
                mode_permissions, mode_class = _get_mode_permission_and_class(
                    newmode)
                current_mode = newmode
                mode = mode_class()
                html.request.set_var("mode",
                                     newmode)  # will be used by makeuri

                # Check general permissions for the new mode
                if mode_permissions is not None and not config.user.may(
                        "wato.seeall"):
                    for pname in mode_permissions:
                        if '.' not in pname:
                            pname = "wato." + pname
                        config.user.need_permission(pname)

        except MKUserError as e:
            action_message = "%s" % e
            html.add_user_error(e.varname, action_message)

        except MKAuthException as e:
            reason = e.args[0]
            action_message = reason
            html.add_user_error(None, reason)

    breadcrumb = make_main_menu_breadcrumb(
        mode.main_menu()) + mode.breadcrumb()
    page_menu = mode.page_menu(breadcrumb)
    wato_html_head(title=mode.title(),
                   breadcrumb=breadcrumb,
                   page_menu=page_menu,
                   show_body_start=display_options.enabled(display_options.H),
                   show_top_heading=display_options.enabled(display_options.T))

    if not html.is_transaction() or (cmk.gui.watolib.read_only.is_enabled() and
                                     cmk.gui.watolib.read_only.may_override()):
        _show_read_only_warning()

    # Show outcome of action
    if html.has_user_errors():
        html.show_user_errors()
    elif action_message:
        html.show_message(action_message)

    # Show content
    mode.handle_page()

    if is_sidebar_reload_needed():
        html.reload_sidebar()

    if config.wato_use_git and html.is_transaction():
        do_git_commit()

    wato_html_footer(show_footer=display_options.enabled(display_options.Z),
                     show_body_end=display_options.enabled(display_options.H))
Exemple #6
0
        def _validating_wrapper(param):
            # TODO: Better error messages, pointing to the location where variables are missing

            def _format_fields(_messages: Union[List, Dict]) -> str:
                if isinstance(_messages, list):
                    return ', '.join(_messages)
                if isinstance(_messages, dict):
                    return ', '.join(_messages.keys())
                return ''

            def _problem(exc_, status_code=400):
                if isinstance(exc_.messages, dict):
                    messages = exc_.messages
                else:
                    messages = {'exc': exc_.messages}
                return problem(
                    status=status_code,
                    title=http.client.responses[status_code],
                    detail=
                    f"These fields have problems: {_format_fields(exc_.messages)}",
                    ext={'fields': messages},
                )

            if (self.method in ("post", "put") and request.get_data(cache=True)
                    and request.content_type != self.content_type):
                return problem(
                    status=415,
                    title=
                    f"Content type {request.content_type!r} not supported on this endpoint.",
                )

            try:
                if path_schema:
                    param.update(path_schema().load(param))
            except ValidationError as exc:
                return _problem(exc, status_code=404)

            try:
                if query_schema:
                    param.update(query_schema().load(
                        _from_multi_dict(request.args)))

                if header_schema:
                    param.update(header_schema().load(request.headers))

                if request_schema:
                    # Try to decode only when there is data. Decoding an empty string will fail.
                    if request.get_data(cache=True):
                        json = request.json or {}
                    else:
                        json = {}
                    param['body'] = request_schema().load(json)
            except ValidationError as exc:
                return _problem(exc, status_code=400)

            # make pylint happy
            assert callable(self.func)
            # FIXME
            # We need to get the "original data" somewhere and are currently "piggy-backing"
            # it on the response instance. This is somewhat problematic because it's not
            # expected behaviour and not a valid interface of Response. Needs refactoring.

            if not self.skip_locking and self.method != 'get':
                with store.lock_checkmk_configuration():
                    response = self.func(param)
            else:
                response = self.func(param)

            response.freeze()

            if self.output_empty and response.status_code < 400 and response.data:
                return problem(status=500,
                               title="Unexpected data was sent.",
                               detail=(f"Endpoint {self.operation_id}\n"
                                       "This is a bug, please report."),
                               ext={'data_sent': str(response.data)})

            if self.output_empty:
                response.content_type = None

            if response.status_code not in self._expected_status_codes:
                return problem(
                    status=500,
                    title=
                    f"Unexpected status code returned: {response.status_code}",
                    detail=(f"Endpoint {self.operation_id}\n"
                            "This is a bug, please report."),
                    ext={'codes': self._expected_status_codes})

            # We assume something has been modified and increase the config generation ID
            # by one. This is necessary to ensure a warning in the "Activate Changes" GUI
            # about there being new changes to activate can be given to the user.
            if request.method != 'get' and response.status_code < 300:
                update_config_generation()

                # We assume no configuration change on GET and no configuration change on
                # non-ok responses.
                if config.wato_use_git:
                    do_git_commit()

            if hasattr(response, 'original_data') and response_schema:
                try:
                    response_schema().load(response.original_data)
                    return response
                except ValidationError as exc:
                    # Hope we never get here in production.
                    return problem(
                        status=500,
                        title="Server was about to send an invalid response.",
                        detail="This is an error of the implementation.",
                        ext={
                            'errors': exc.messages,
                            'orig': response.original_data
                        },
                    )

            return response