如何覆盖Flask-Security默认消息?

时间:2014-04-17 14:34:11

标签: python flask flask-security

Flask-Security从Python Flask Web应用程序开发的认证和授权中获取了许多繁重的工作。不过,我遇到了一个障碍。

在登录页面上,消息会闪烁以响应各种无效输入。例子包括: - 指定的用户不存在 - 无效的密码 - 帐户已停用

这不符合安全最佳做法。您不应向用户透露他或她的登录尝试被拒绝的详细信息。上述消息使黑客更容易识别有效的用户名。

我想覆盖这些标准的Flask-Security消息,用"无效的用户名或密码替换它们。"但是,我还没有找到一种方便的方法。

这些消息存储在site-packages / flask_security / core.py中的_default_messages中。我可以修改该文件,但这不是一个好的解决方案:如果我重新安装或更新Flask-Security,它将会中断。

我知道我可以customize Flask-Security's default views。但是视图包含有用的代码,例如

{{ render_field_with_errors(login_user_form.email) }}

隐藏登录表单的实现细节。我不想丢弃所有有用的代码,只是为了更改一些消息而重写大部分代码。

有谁知道更好的方法来自定义Flask-Security的登录消息?

3 个答案:

答案 0 :(得分:6)

Rachel部分正确(顺便说一句,所有消息都在flask-security中的core.py中)。通过更改默认安全消息,您可以使错误消息更广泛。但是,假设您使用的是字段的标准呈现,错误消息仍将附加到导致问题的表单元素。因此,不难理解问题出在用户名或密码中。

我做了什么:

  1. 将配置文件中的消息更改为广泛消息。我更改了以下消息:

    SECURITY_MSG_INVALID_PASSWORD = ("Bad username or password", "error")
    SECURITY_MSG_PASSWORD_NOT_PROVIDED = ("Bad username or password", "error")
    SECURITY_MSG_USER_DOES_NOT_EXIST = ("Bad username or password", "error")
    
  2. 使用呈现没有错误消息(render_field)的字段的宏。如果你正在使用flask-bootstrap,那么就没有这样的宏,所以我创建了一个我自己的(非常简单,只需删除错误块,以及为表单元素着色的类)。下面只是从flask-bootstrap复制+粘贴,只删除了字段错误代码:

    {% macro bootstrap_form_field_no_errors(field,
                        form_type="basic",
                        horizontal_columns=('lg', 2, 10),
                        button_map={}) %}
    {% if field.widget.input_type == 'checkbox' %}
      {% call _hz_form_wrap(horizontal_columns, form_type, True) %}
        <div class="checkbox">
          <label>
            {{field()|safe}} {{field.label.text|safe}}
          </label>
        </div>
      {% endcall %}
    {%- elif field.type == 'RadioField' -%}
      {# note: A cleaner solution would be rendering depending on the widget,
         this is just a hack for now, until I can think of something better #}
      {% call _hz_form_wrap(horizontal_columns, form_type, True) %}
        {% for item in field -%}
          <div class="radio">
            <label>
              {{item|safe}} {{item.label.text|safe}}
            </label>
          </div>
        {% endfor %}
      {% endcall %}
    {%- elif field.type == 'SubmitField' -%}
      {# note: same issue as above - should check widget, not field type #}
      {% call _hz_form_wrap(horizontal_columns, form_type, True) %}
        {{field(class='btn btn-%s' % button_map.get(field.name, 'default'))}}
      {% endcall %}
    {%- elif field.type == 'FormField' -%}
    {# note: FormFields are tricky to get right and complex setups requiring
       these are probably beyond the scope of what this macro tries to do.
       the code below ensures that things don't break horribly if we run into
       one, but does not try too hard to get things pretty. #}
      <fieldset>
        <legend>{{field.label}}</legend>
        {%- for subfield in field %}
          {% if not bootstrap_is_hidden_field(subfield) -%}
            {{ form_field(subfield,
                          form_type=form_type,
                          horizontal_columns=horizontal_columns,
                          button_map=button_map) }}
          {%- endif %}
        {%- endfor %}
      </fieldset>
    {% else -%}
      <div class="form-group">
          {%- if form_type == "inline" %}
            {{field.label(class="sr-only")|safe}}
            {{field(class="form-control", placeholder=field.description, **kwargs)|safe}}
          {% elif form_type == "horizontal" %}
            {{field.label(class="control-label " + (
              " col-%s-%s" % horizontal_columns[0:2]
            ))|safe}}
            <div class=" col-{{horizontal_columns[0]}}-{{horizontal_columns[2]}}">
              {{field(class="form-control", **kwargs)|safe}}
            </div>
            {%- if field.description -%}
              {% call _hz_form_wrap(horizontal_columns, form_type) %}
                <p class="help-block">{{field.description|safe}}</p>
              {% endcall %}
            {%- endif %}
          {%- else -%}
            {{field.label(class="control-label")|safe}}
            {{field(class="form-control", **kwargs)|safe}}
    
            {%- if field.errors %}
              {%- for error in field.errors %}
                <p class="help-block">{{error}}</p>
              {%- endfor %}
            {%- elif field.description -%}
              <p class="help-block">{{field.description|safe}}</p>
            {%- endif %}
          {%- endif %}
      </div>
    {% endif %}
    {% endmacro %}
    
  3. 创建了一个新宏,它呈现了所有字段的所有错误,并将其放在表单的顶部。我没有看到同时生成多个错误,但您并不确切知道哪个字段导致了错误。同样,我的代码与flask-bootstrap样式匹配,但您可以轻松删除特定于引导程序的元素。

    {% macro fields_errors() %}
    {% for field in varargs %}
    {% if field.errors %}
      {% for error in field.errors %}
        {% call _hz_form_wrap(horizontal_columns, form_type) %}
          <div class="alert alert-danger">{{error}}</div>
        {% endcall %}
      {% endfor %}
    {% endif %}
    {% endfor %}
    {% endmacro %}
    
  4. 在表单中,您可以使用所有表单字段调用此宏:

        {{ fields_errors(login_user_form.email, login_user_form.password, login_user_form.remember) }}`
    

答案 1 :(得分:5)

通过阅读代码,它看起来像是在所有消息上设置配置默认值:

_default_messages = {
    'INVALID_PASSWORD': ('Invalid password', 'error'),
}

... later on in the init method ....

for key, value in _default_messages.items():
        app.config.setdefault('SECURITY_MSG_' + key, value)

要更改消息,看起来您只需在app.config中设置此消息:

SECURITY_MSG_INVALID_PASSWORD = ('Your username and password do not match our records', 'error'),

如果它没有,看起来重构模块以使用babel或类似的东西并不困难。作为一个优秀的FOSS公民,这将是一件好事。

答案 2 :(得分:0)

起初,我使用了Rachel的答案,但是由于错误仍然附加在表单字段中,最终用户可以判断他们是否具有有效的电子邮件和不正确的密码...因此,由于我使用了Flash消息来获取几乎所有反馈,我发现编辑flask_security本身对我来说是最简单的方法。

如果您打开文件

site-packages \ flask_security \ forms.py

您将找到以下代码:

    if self.user is None:
        self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0])
        return False
    if not self.user.password:
        self.password.errors.append(get_message('PASSWORD_NOT_SET')[0])
        return False
    if not verify_and_update_password(self.password.data, self.user):
        self.password.errors.append(get_message('INVALID_PASSWORD')[0])
        return False
    if requires_confirmation(self.user):
        self.email.errors.append(get_message('CONFIRMATION_REQUIRED')[0])
        return False
    if not self.user.is_active:
        self.email.errors.append(get_message('DISABLED_ACCOUNT')[0])
        return False

您可以看到它在相关字段(电子邮件或密码)后附加了一个错误,您可以将其注释掉以仅不提供有关失败的反馈-我个人如下所示:

    if self.user is None:
        #self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0])
        flash('Error: There was an issue logging you in')
        return False
    if not self.user.password:
        #self.password.errors.append(get_message('PASSWORD_NOT_SET')[0])
        flash('Error: There was an issue logging you in')
        return False
    if not verify_and_update_password(self.password.data, self.user):
        #self.password.errors.append(get_message('INVALID_PASSWORD')[0])
        flash('Error: There was an issue logging you in')
        return False
    if requires_confirmation(self.user):
        #self.email.errors.append(get_message('CONFIRMATION_REQUIRED')[0])
        flash('Error: You have not confirmed your email')
        return False
    if not self.user.is_active:
        #self.email.errors.append(get_message('DISABLED_ACCOUNT')[0])
        flash('Error: There was an issue logging you in')
        return False

与直接编辑软件包相比,我不确定是否有更好的方法来实现我所做的更改(我确定这是一种不好的做法),但是它对我有用,并且我理解。 / p>