Exemple #1
0
    def validate_password(self, field):
        # Before we try to validate the user's password, we'll first to check to see if
        # they are disabled.
        userid = self.user_service.find_userid(self.username.data)
        if userid is not None:
            is_disabled, disabled_for = self.user_service.is_disabled(userid)
            if is_disabled and disabled_for == DisableReason.CompromisedPassword:
                raise wtforms.validators.ValidationError(
                    markupsafe.Markup(self.breach_service.failure_message))

        # Do our typical validation of the password.
        super().validate_password(field)

        # If we have a user ID, then we'll go and check it against our breached password
        # service. If the password has appeared in a breach or is otherwise compromised
        # we will disable the user and reject the login.
        if userid is not None:
            if self.breach_service.check_password(
                    field.data, tags=["method:auth",
                                      "auth_method:login_form"]):
                user = self.user_service.get_user(userid)
                send_password_compromised_email_hibp(self.request, user)
                self.user_service.disable_password(
                    user.id, reason=DisableReason.CompromisedPassword)
                raise wtforms.validators.ValidationError(
                    markupsafe.Markup(self.breach_service.failure_message))
Exemple #2
0
    def validate_password(self, field):
        # Before we try to validate the user's password, we'll first to check to see if
        # they are disabled.
        userid = self.user_service.find_userid(self.username.data)
        if userid is not None:
            is_disabled, disabled_for = self.user_service.is_disabled(userid)
            if is_disabled and disabled_for == DisableReason.CompromisedPassword:
                raise wtforms.validators.ValidationError(
                    jinja2.Markup(self.breach_service.failure_message)
                )

        # Do our typical validation of the password.
        super().validate_password(field)

        # If we have a user ID, then we'll go and check it against our breached password
        # service. If the password has appeared in a breach or is otherwise compromised
        # we will disable the user and reject the login.
        if userid is not None:
            if self.breach_service.check_password(
                field.data, tags=["method:auth", "auth_method:login_form"]
            ):
                user = self.user_service.get_user(userid)
                send_password_compromised_email_hibp(self.request, user)
                self.user_service.disable_password(
                    user.id, reason=DisableReason.CompromisedPassword
                )
                raise wtforms.validators.ValidationError(
                    jinja2.Markup(self.breach_service.failure_message)
                )
Exemple #3
0
def _basic_auth_login(username, password, request):
    login_service = request.find_service(IUserService, context=None)
    breach_service = request.find_service(IPasswordBreachedService,
                                          context=None)

    userid = login_service.find_userid(username)
    if userid is not None:
        user = login_service.get_user(userid)
        is_disabled, disabled_for = login_service.is_disabled(user.id)
        if is_disabled and disabled_for == DisableReason.CompromisedPassword:
            # This technically violates the contract a little bit, this function is
            # meant to return None if the user cannot log in. However we want to present
            # a different error message than is normal when we're denying the log in
            # becasue of a compromised password. So to do that, we'll need to raise a
            # HTTPError that'll ultimately get returned to the client. This is OK to do
            # here because we've already successfully authenticated the credentials, so
            # it won't screw up the fall through to other authentication mechanisms
            # (since we wouldn't have fell through to them anyways).
            raise _format_exc_status(BasicAuthBreachedPassword(),
                                     breach_service.failure_message_plain)
        elif login_service.check_password(
                user.id, password, tags=["method:auth", "auth_method:basic"]):
            if breach_service.check_password(
                    password, tags=["method:auth", "auth_method:basic"]):
                send_password_compromised_email_hibp(request, user)
                login_service.disable_password(
                    user.id, reason=DisableReason.CompromisedPassword)
                raise _format_exc_status(BasicAuthBreachedPassword(),
                                         breach_service.failure_message_plain)
            else:
                login_service.update_user(
                    user.id, last_login=datetime.datetime.utcnow())
                return _authenticate(user.id, request)
Exemple #4
0
def _basic_auth_check(username, password, request):
    request.authentication_method = AuthenticationMethod.BASIC_AUTH

    # Basic authentication can only be used for uploading
    if request.matched_route.name not in ["forklift.legacy.file_upload"]:
        return

    login_service = request.find_service(IUserService, context=None)
    breach_service = request.find_service(IPasswordBreachedService,
                                          context=None)

    userid = login_service.find_userid(username)
    if userid is not None:
        user = login_service.get_user(userid)
        is_disabled, disabled_for = login_service.is_disabled(user.id)
        if is_disabled and disabled_for == DisableReason.CompromisedPassword:
            # This technically violates the contract a little bit, this function is
            # meant to return None if the user cannot log in. However we want to present
            # a different error message than is normal when we're denying the log in
            # because of a compromised password. So to do that, we'll need to raise a
            # HTTPError that'll ultimately get returned to the client. This is OK to do
            # here because we've already successfully authenticated the credentials, so
            # it won't screw up the fall through to other authentication mechanisms
            # (since we wouldn't have fell through to them anyways).
            raise _format_exc_status(BasicAuthBreachedPassword(),
                                     breach_service.failure_message_plain)
        elif login_service.check_password(
                user.id,
                password,
                tags=[
                    "mechanism:basic_auth", "method:auth", "auth_method:basic"
                ],
        ):
            if breach_service.check_password(
                    password, tags=["method:auth", "auth_method:basic"]):
                send_password_compromised_email_hibp(request, user)
                login_service.disable_password(
                    user.id, reason=DisableReason.CompromisedPassword)
                raise _format_exc_status(BasicAuthBreachedPassword(),
                                         breach_service.failure_message_plain)

            login_service.update_user(user.id,
                                      last_login=datetime.datetime.utcnow())
            return _authenticate(user.id, request)
        else:
            user.record_event(
                tag="account:login:failure",
                ip_address=request.remote_addr,
                additional={
                    "reason": "invalid_password",
                    "auth_method": "basic"
                },
            )
            raise _format_exc_status(
                BasicAuthFailedPassword(),
                "Invalid or non-existent authentication information. "
                "See {projecthelp} for more information.".format(
                    projecthelp=request.help_url(_anchor="invalid-auth")),
            )
Exemple #5
0
def _basic_auth_login(username, password, request):
    login_service = request.find_service(IUserService, context=None)
    breach_service = request.find_service(IPasswordBreachedService, context=None)

    userid = login_service.find_userid(username)
    if userid is not None:
        user = login_service.get_user(userid)
        is_disabled, disabled_for = login_service.is_disabled(user.id)
        if is_disabled and disabled_for == DisableReason.CompromisedPassword:
            # This technically violates the contract a little bit, this function is
            # meant to return None if the user cannot log in. However we want to present
            # a different error message than is normal when we're denying the log in
            # becasue of a compromised password. So to do that, we'll need to raise a
            # HTTPError that'll ultimately get returned to the client. This is OK to do
            # here because we've already successfully authenticated the credentials, so
            # it won't screw up the fall through to other authentication mechanisms
            # (since we wouldn't have fell through to them anyways).
            raise _format_exc_status(
                BasicAuthBreachedPassword(), breach_service.failure_message_plain
            )
        elif login_service.check_password(
            user.id, password, tags=["method:auth", "auth_method:basic"]
        ):
            if breach_service.check_password(
                password, tags=["method:auth", "auth_method:basic"]
            ):
                send_password_compromised_email_hibp(request, user)
                login_service.disable_password(
                    user.id, reason=DisableReason.CompromisedPassword
                )
                raise _format_exc_status(
                    BasicAuthBreachedPassword(), breach_service.failure_message_plain
                )
            else:
                login_service.update_user(
                    user.id, last_login=datetime.datetime.utcnow()
                )
                return _authenticate(user.id, request)
Exemple #6
0
    def test_password_compromised_email_hibp(self, pyramid_request,
                                             pyramid_config, monkeypatch,
                                             verified):
        stub_user = pretend.stub(
            username="******",
            name="",
            email="*****@*****.**",
            primary_email=pretend.stub(email="*****@*****.**",
                                       verified=verified),
        )
        subject_renderer = pyramid_config.testing_add_renderer(
            "email/password-compromised-hibp/subject.txt")
        subject_renderer.string_response = "Email Subject"
        body_renderer = pyramid_config.testing_add_renderer(
            "email/password-compromised-hibp/body.txt")
        body_renderer.string_response = "Email Body"
        html_renderer = pyramid_config.testing_add_renderer(
            "email/password-compromised-hibp/body.html")
        html_renderer.string_response = "Email HTML Body"

        send_email = pretend.stub(
            delay=pretend.call_recorder(lambda *args, **kwargs: None))
        pyramid_request.task = pretend.call_recorder(
            lambda *args, **kwargs: send_email)
        monkeypatch.setattr(email, "send_email", send_email)

        result = email.send_password_compromised_email_hibp(
            pyramid_request, stub_user)

        assert result == {}
        assert pyramid_request.task.calls == [pretend.call(send_email)]
        assert send_email.delay.calls == [
            pretend.call(
                f"{stub_user.username} <{stub_user.email}>",
                attr.asdict(
                    EmailMessage(
                        subject="Email Subject",
                        body_text="Email Body",
                        body_html=(
                            "<html>\n<head></head>\n"
                            "<body><p>Email HTML Body</p></body>\n</html>\n"),
                    )),
            )
        ]