예제 #1
0
def _parse_auth_options(
        auth_details: Dict[str, Union[str, bool]],
        enforce_pw_change: bool = False) -> Dict[str, Union[int, str, bool]]:
    """Format the authentication information to be Checkmk compatible

    Args:
        auth_details:
            user provided authentication details

    Returns:
        formatted authentication details for Checkmk user_attrs

    Example:
    >>> _parse_auth_options({"auth_type": "automation", "secret": "TNBJCkwane3$cfn0XLf6p6a"})  # doctest:+ELLIPSIS
    {'automation_secret': 'TNBJCkwane3$cfn0XLf6p6a', 'password': ...}

    >>> _parse_auth_options({"auth_type": "password", "password": "******"}, enforce_pw_change=True)  # doctest:+ELLIPSIS
    {'password': ..., 'last_pw_change': ..., 'enforce_pw_change': True}
    """
    auth_options: Dict[str, Union[str, bool, int]] = {}
    if auth_details:
        if auth_details["auth_type"] == "automation":
            secret = auth_details["secret"]
            auth_options["automation_secret"] = secret
            auth_options["password"] = htpasswd.hash_password(secret)
        else:  # password
            auth_options["password"] = htpasswd.hash_password(
                auth_details["password"])
            auth_options["last_pw_change"] = int(time.time())

            # this can be configured in WATO but we enforce this here
            if enforce_pw_change:
                auth_options["enforce_pw_change"] = True

    return auth_options
예제 #2
0
    def _edit_users(self, request):
        # A dictionary with the userid as key
        # Each value is a {"set_attributes": {"alias": "test"},
        #                  "unset_attributes": ["pager", "email"]}
        user_settings = request.get("users")
        all_users = userdb.load_users()

        edit_user_objects = {}
        for user_id, settings in user_settings.items():
            if user_id not in all_users:
                raise MKUserError(None, _("Unknown user: %s") % user_id)

            if all_users[user_id].get("connector", "htpasswd") != "htpasswd":
                raise MKUserError(
                    None,
                    _("This user is not a htpasswd user: %s") % user_id)

            user_attrs = copy.deepcopy(all_users[user_id])
            user_attrs.update(settings.get("set_attributes", {}))
            for entry in settings.get("unset_attributes", []):
                if entry not in user_attrs:
                    continue
                del user_attrs[entry]

            new_password = settings.get("set_attributes", {}).get("password")
            if new_password:
                user_attrs["password"] = hash_password(new_password)
                user_attrs["serial"] = user_attrs.get("serial", 0) + 1

            edit_user_objects[user_id] = {
                "attributes": user_attrs,
                "is_new_user": False
            }

        cmk.gui.watolib.users.edit_users(edit_user_objects)
예제 #3
0
    def _action(self) -> None:
        assert user.id is not None

        users = userdb.load_users(lock=True)
        user_spec = users[user.id]

        cur_password = request.get_str_input_mandatory("cur_password")
        password = request.get_str_input_mandatory("password")
        password2 = request.get_str_input_mandatory("password2", "")

        # 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 userdb.check_credentials(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)
        user_spec["password"] = hash_password(password)
        user_spec["last_pw_change"] = int(time.time())

        # In case the user was enforced to change it's password, remove the flag
        try:
            del user_spec["enforce_pw_change"]
        except KeyError:
            pass

        # Increase serial to invalidate old authentication cookies
        if "serial" not in user_spec:
            user_spec["serial"] = 1
        else:
            user_spec["serial"] += 1

        userdb.save_users(users)

        flash(_("Successfully changed password."))

        # Set the new cookie to prevent logout for the current user
        login.update_auth_cookie(user.id)

        # In distributed setups with remote sites where the user can login, start the
        # user profile replication now which will redirect the user to the destination
        # page after completion. Otherwise directly open up the destination page.
        origtarget = request.get_str_input_mandatory("_origtarget", "user_change_pw.py")
        if user.authorized_login_sites():
            raise redirect(
                makeuri_contextless(
                    request, [("back", origtarget)], filename="user_profile_replicate.py"
                )
            )
        raise redirect(origtarget)
def test_hash_password() -> None:
    # Suppress this warning from passlib code. We can not do anything about this and it clutters our
    # unit test log
    # tests/unit/cmk/gui/test_userdb_htpasswd_connector.py::test_hash_password
    # (...)/handlers/bcrypt.py:378: DeprecationWarning: NotImplemented should not be used in a boolean context
    # if not result:
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", category=DeprecationWarning)
        hashed_pw = htpasswd.hash_password("blä")
    assert bcrypt.verify("blä", hashed_pw)
예제 #5
0
파일: webapi.py 프로젝트: petrows/checkmk
    def _add_users(self, request):
        users_from_request = request.get("users")
        new_user_objects = {}
        for user_id, values in users_from_request.items():
            user_template = userdb.new_user_template("htpasswd")
            if "password" in values:
                values["password"] = hash_password(values["password"])
                values["serial"] = 1

            user_template.update(values)
            new_user_objects[user_id] = {"attributes": user_template, "is_new_user": True}
        cmk.gui.watolib.users.edit_users(new_user_objects)
예제 #6
0
    def _action(self) -> bool:
        assert config.user.id is not None

        users = userdb.load_users(lock=True)
        user = users[config.user.id]

        cur_password = html.request.get_str_input_mandatory('cur_password')
        password = html.request.get_str_input_mandatory('password')
        password2 = html.request.get_str_input_mandatory('password2', '')

        # 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 userdb.check_credentials(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)
        user['password'] = hash_password(password)
        user['last_pw_change'] = int(time.time())

        # In case the user was enforced to change it's password, remove the flag
        try:
            del user['enforce_pw_change']
        except KeyError:
            pass

        # Increase serial to invalidate old authentication cookies
        if 'serial' not in user:
            user['serial'] = 1
        else:
            user['serial'] += 1

        userdb.save_users(users)

        # Set the new cookie to prevent logout for the current user
        login.update_auth_cookie(config.user.id)

        return True
예제 #7
0
파일: webapi.py 프로젝트: m3rlinux/checkmk
    def _edit_users(self, request):
        # A dictionary with the userid as key
        # Each value is a {"set_attributes": {"alias": "test"},
        #                  "unset_attributes": ["pager", "email"]}
        user_settings = request.get("users")
        all_users = userdb.load_users()

        locked_attributes_by_connection = {}

        edit_user_objects = {}
        for user_id, settings in user_settings.items():
            if user_id not in all_users:
                raise MKUserError(None, _("Unknown user: %s") % user_id)

            user = all_users[user_id]
            connector_id = user.get("connector")

            set_attributes = settings.get("set_attributes", {})
            unset_attributes = settings.get("unset_attributes", [])

            if connector_id not in locked_attributes_by_connection:
                locked_attributes_by_connection[connector_id] = userdb.locked_attributes(
                    connector_id
                )

            locked_attributes = locked_attributes_by_connection[connector_id]
            for attr in list(set_attributes.keys()) + unset_attributes:
                if attr in locked_attributes:
                    raise MKUserError(
                        None,
                        _(
                            'Attribute "%s" of user "%s" can not be changed, '
                            "because it is locked by the user connection."
                        )
                        % (attr, user_id),
                    )

            user_attrs = copy.deepcopy(user)
            user_attrs.update(set_attributes)
            for entry in unset_attributes:
                if entry not in user_attrs:
                    continue
                # TODO: Dynamically fiddling around with a TypedDict is a bit questionable
                del user_attrs[entry]  # type: ignore[misc]

            new_password = set_attributes.get("password")
            if new_password:
                user_attrs["password"] = hash_password(new_password)
                user_attrs["serial"] = user_attrs.get("serial", 0) + 1

            edit_user_objects[user_id] = {"attributes": user_attrs, "is_new_user": False}

        cmk.gui.watolib.users.edit_users(edit_user_objects)
예제 #8
0
def _auth_options_to_internal_format(
        auth_details: AuthOptions,
        new_user: bool = False) -> Dict[str, Union[int, str, bool]]:
    """Format the authentication information to be Checkmk compatible

    Args:
        auth_details:
            user provided authentication details

    Returns:
        formatted authentication details for Checkmk user_attrs

    Example:
    >>> _auth_options_to_internal_format({"auth_type": "automation", "secret": "TNBJCkwane3$cfn0XLf6p6a"})  # doctest:+ELLIPSIS
    {'automation_secret': 'TNBJCkwane3$cfn0XLf6p6a', 'password': ...}

    >>> _auth_options_to_internal_format({"auth_type": "password", "password": "******"})  # doctest:+ELLIPSIS
    {'password': ..., 'last_pw_change': ...}
    """
    internal_options: Dict[str, Union[str, bool, int]] = {}
    if not auth_details:
        return internal_options

    if auth_details["auth_type"] == "automation":
        secret = auth_details["secret"]
        internal_options["automation_secret"] = secret
        internal_options["password"] = htpasswd.hash_password(secret)
    else:  # password
        if new_user or "password" in auth_details:
            internal_options["password"] = htpasswd.hash_password(
                auth_details["password"])
            internal_options["last_pw_change"] = int(time.time())

        if "enforce_password_change" in auth_details:
            internal_options["enforce_pw_change"] = auth_details[
                "enforce_password_change"]

    return internal_options
예제 #9
0
def _mk_user_obj(username, password, automation=False):
    user = {
        username: {
            'attributes': {
                'alias': username,
                'email': '*****@*****.**',
                'password': htpasswd.hash_password(password),
                'notification_method': 'email',
                'roles': ['admin'],
                'serial': 0
            },
            'is_new_user': True,
        }
    }  # type: dict
    if automation:
        user[username]['attributes'].update(automation_secret=password, )
    return user
예제 #10
0
def test_check_credentials_local_user_create_htpasswd_user_ad_hoc() -> None:
    user_id = UserId("someuser")
    assert userdb.user_exists(user_id) is False
    assert userdb._user_exists_according_to_profile(user_id) is False
    assert user_id not in _load_users_uncached(lock=False)

    htpasswd.Htpasswd(Path(cmk.utils.paths.htpasswd_file)).save(
        {user_id: htpasswd.hash_password("cmk")})
    # Once a user exists in the htpasswd, the GUI treats the user as existing user and will
    # automatically initialize the missing data structures
    assert userdb.user_exists(user_id) is True
    assert userdb._user_exists_according_to_profile(user_id) is False
    assert str(user_id) in _load_users_uncached(lock=False)

    assert userdb.check_credentials(user_id, "cmk") == user_id

    # Nothing changes during regular access
    assert userdb.user_exists(user_id) is True
    assert userdb._user_exists_according_to_profile(user_id) is False
    assert str(user_id) in _load_users_uncached(lock=False)
예제 #11
0
def test_hash_password():
    hashed_pw = htpasswd.hash_password("blä")
    assert sha256_crypt.verify("blä", hashed_pw)

    hashed_pw = htpasswd.hash_password("blä")
    assert sha256_crypt.verify("blä", hashed_pw)
예제 #12
0
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()
예제 #13
0
    def action(self):
        if not html.check_transaction():
            return "users"

        if self._is_new_user:
            self._user_id = UserID(allow_empty=False).from_html_vars("user_id")
            user_attrs = {}
        else:
            self._user_id = html.get_unicode_input("edit").strip()
            user_attrs = self._users[self._user_id]

        # Full name
        user_attrs["alias"] = html.get_unicode_input("alias").strip()

        # Locking
        user_attrs["locked"] = html.get_checkbox("locked")
        increase_serial = False

        if self._user_id in self._users and self._users[
                self._user_id]["locked"] != user_attrs["locked"] and user_attrs["locked"]:
            increase_serial = True  # when user is being locked now, increase the auth serial

        # Authentication: Password or Secret
        auth_method = html.request.var("authmethod")
        if auth_method == "secret":
            secret = html.request.var("_auth_secret", "").strip()
            user_attrs["automation_secret"] = secret
            user_attrs["password"] = hash_password(secret)
            increase_serial = True  # password changed, reflect in auth serial

        else:
            password = html.request.var("_password_" + self._pw_suffix(), '').strip()
            password2 = html.request.var("_password2_" + self._pw_suffix(), '').strip()

            # We compare both passwords only, if the user has supplied
            # the repeation! We are so nice to our power users...
            # Note: this validation is done before the main-validiation later on
            # It doesn't make any sense to put this block into the main validation function
            if password2 and password != password2:
                raise MKUserError("_password2", _("The both passwords do not match."))

            # Detect switch back from automation to password
            if "automation_secret" in user_attrs:
                del user_attrs["automation_secret"]
                if "password" in user_attrs:
                    del user_attrs["password"]  # which was the encrypted automation password!

            if password:
                user_attrs["password"] = hash_password(password)
                user_attrs["last_pw_change"] = int(time.time())
                increase_serial = True  # password changed, reflect in auth serial

            # PW change enforcement
            user_attrs["enforce_pw_change"] = html.get_checkbox("enforce_pw_change")
            if user_attrs["enforce_pw_change"]:
                increase_serial = True  # invalidate all existing user sessions, enforce relogon

        # Increase serial (if needed)
        if increase_serial:
            user_attrs["serial"] = user_attrs.get("serial", 0) + 1

        # Email address
        user_attrs["email"] = EmailAddressUnicode().from_html_vars("email")

        idle_timeout = watolib.get_vs_user_idle_timeout().from_html_vars("idle_timeout")
        user_attrs["idle_timeout"] = idle_timeout
        if idle_timeout is not None:
            user_attrs["idle_timeout"] = idle_timeout
        elif idle_timeout is None and "idle_timeout" in user_attrs:
            del user_attrs["idle_timeout"]

        # Pager
        user_attrs["pager"] = html.request.var("pager", '').strip()

        if cmk.is_managed_edition():
            customer = self._vs_customer.from_html_vars("customer")
            self._vs_customer.validate_value(customer, "customer")

            if customer != managed.default_customer_id():
                user_attrs["customer"] = customer
            elif "customer" in user_attrs:
                del user_attrs["customer"]

        vs_sites = self._vs_sites()
        authorized_sites = vs_sites.from_html_vars("authorized_sites")
        vs_sites.validate_value(authorized_sites, "authorized_sites")

        if authorized_sites is not None:
            user_attrs["authorized_sites"] = authorized_sites
        elif "authorized_sites" in user_attrs:
            del user_attrs["authorized_sites"]

        # Roles
        user_attrs["roles"] = [
            role for role in self._roles.keys() if html.get_checkbox("role_" + role)
        ]

        # Language configuration
        set_lang = html.get_checkbox("_set_lang")
        language = html.request.var("language")
        if set_lang:
            if language == "":
                language = None
            user_attrs["language"] = language
        elif not set_lang and "language" in user_attrs:
            del user_attrs["language"]

        # Contact groups
        cgs = []
        for c in self._contact_groups:
            if html.get_checkbox("cg_" + c):
                cgs.append(c)
        user_attrs["contactgroups"] = cgs

        # Notification settings are only active if we do *not* have
        # rule based notifications!
        if not self._rbn_enabled():
            # Notifications
            user_attrs["notifications_enabled"] = html.get_checkbox("notifications_enabled")

            ntp = html.request.var("notification_period")
            if ntp not in self._timeperiods:
                ntp = "24X7"
            user_attrs["notification_period"] = ntp

            for what, opts in [("host", "durfs"), ("service", "wucrfs")]:
                user_attrs[what + "_notification_options"] = "".join(
                    [opt for opt in opts if html.get_checkbox(what + "_" + opt)])

            value = watolib.get_vs_flexible_notifications().from_html_vars("notification_method")
            user_attrs["notification_method"] = value
        else:
            user_attrs["fallback_contact"] = html.get_checkbox("fallback_contact")

        # Custom user attributes
        for name, attr in userdb.get_user_attributes():
            value = attr.valuespec().from_html_vars('ua_' + name)
            user_attrs[name] = value

        # Generate user "object" to update
        user_object = {self._user_id: {"attributes": user_attrs, "is_new_user": self._is_new_user}}
        # The following call validates and updated the users
        edit_users(user_object)
        return "users"