def test_unicode_attempts():
    user_id = db_utils.create_user(password=u"password", username=user_name)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=u"passwordé",
                                       request=None)
    assert result == (0, 'invalid')
    result = login.authenticate_bcrypt(username=user_name,
                                       password=u"passwörd",
                                       request=None)
    assert result == (0, 'invalid')
    result = login.authenticate_bcrypt(username=user_name,
                                       password=u"password",
                                       request=None)
    assert result == (user_id, None)
Example #2
0
def authorize_post_(request):
    form = request.web_input(credentials='',
                             username='',
                             password='',
                             remember_me='',
                             mobile='',
                             not_me='')
    try:
        credentials = json.loads(form.credentials)
    except ValueError:
        raise HTTPBadRequest()
    scopes = credentials.pop('scopes')
    error = None
    if form.not_me and form.username:
        userid, error = login.authenticate_bcrypt(
            form.username,
            form.password,
            request=request if form.remember_me else None)
        if error:
            error = errorcode.login_errors.get(error, 'Unknown error.')
    elif not request.userid:
        error = "You must specify a username and password."
    else:
        userid = request.userid
    if error:
        return Response(
            render_form(request, scopes, credentials, bool(form.mobile), error,
                        form.username, form.password, bool(form.remember_me),
                        bool(form.not_me)))
    credentials['userid'] = userid
    response_attrs = server.create_authorization_response(
        *(extract_params(request) + (scopes, credentials)))
    return OAuthResponse(*response_attrs)
Example #3
0
 def POST(self):
     form = web.input(credentials='', username='', password='', remember_me='', mobile='', not_me='')
     try:
         credentials = json.loads(form.credentials)
     except ValueError:
         raise web.badrequest()
     scopes = credentials.pop('scopes')
     error = None
     if form.not_me and form.username:
         userid, error = login.authenticate_bcrypt(form.username, form.password, bool(form.remember_me))
         if error:
             error = errorcode.login_errors.get(error, 'Unknown error.')
     elif not self.user_id:
         error = "You must specify a username and password."
     else:
         userid = self.user_id
     if error:
         return self.render_form(scopes, credentials, bool(form.mobile), error,
                                 form.username, form.password, bool(form.remember_me), bool(form.not_me))
     credentials['userid'] = userid
     headers, body, status = server.create_authorization_response(
         *(extract_params() + (scopes, credentials)))
     for k, v in headers.iteritems():
         web.header(k, v)
     web.ctx.status = '%s Status' % (status,)
     return body
Example #4
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(
            request.userid,
            "control/2fa/generate_recovery_codes_verify_password.html",
            ["password"],
            title="Generate Recovery Codes: Verify Password"
        ))
    # The user has authenticated, so continue with generating the new recovery codes.
    else:
        # Edge case prevention: Stop the user from having two Weasyl sessions open and trying
        #   to proceed through the generation process with two sets of recovery codes.
        invalidate_other_sessions(request.userid)
        # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent
        #   a user from confusing themselves if they visit the request page twice.
        sess = request.weasyl_session
        gen_rec_codes = True
        if '2fa_recovery_codes_timestamp' in sess.additional_data:
            # Are the codes on the current session < 30 minutes old?
            tstamp = sess.additional_data['2fa_recovery_codes_timestamp']
            if arrow.now().timestamp - tstamp < 1800:
                # We have recent codes on the session, use them instead of generating fresh codes.
                recovery_codes = sess.additional_data['2fa_recovery_codes'].split(',')
                gen_rec_codes = False
        if gen_rec_codes:
            # Either this is a fresh request to generate codes, or the timelimit was exceeded.
            recovery_codes = tfa.generate_recovery_codes()
            _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [
            recovery_codes,
            None
        ], title="Generate Recovery Codes: Save New Recovery Codes"))
Example #5
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(
        request.userid),
                                               request.params['password'],
                                               request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/init.html",
                [define.get_display_name(request.userid), "password"],
                title="Enable 2FA: Step 1"))
    # Unlikely that this block will get triggered, but just to be safe, check for it
    elif status == "unicode-failure":
        raise HTTPSeeOther(location='/signin/unicode-failure')
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(
            define.webpage(request.userid,
                           "control/2fa/init_qrcode.html", [
                               define.get_display_name(request.userid),
                               tfa_secret, tfa_qrcode, None
                           ],
                           title="Enable 2FA: Step 2"))
Example #6
0
def test_login_fails_if_user_is_banned():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 'b' WHERE userid = %(id)s",
                     id=user_id)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password)
    assert result == (user_id, 'banned')
Example #7
0
def test_login_fails_for_invalid_auth_and_logs_failure_if_mod_account(
        tmpdir, monkeypatch):
    # Required: Monkeypatch the log directory, and the staff.MODS frozenset
    monkeypatch.setenv(macro.MACRO_SYS_LOG_PATH, tmpdir + "/")
    log_path = '%s%s.%s.log' % (macro.MACRO_SYS_LOG_PATH, 'login.fail',
                                d.get_timestamp())
    user_id = db_utils.create_user(username='******', password=raw_password)
    # Set the moderators in libweasyl/staff.py via monkeypatch
    monkeypatch.setattr(staff, 'MODS', frozenset([user_id]))
    # Ensure we are actually writing to the file by counting the file's lines
    prerun_loglines = 0
    # The file might not exist; this is fine; ignore
    try:
        with open(log_path, 'r') as log:
            for line in log:
                prerun_loglines += 1
            log.close()
    except IOError:
        pass
    postrun_loglines = 0
    # Item under test
    result = login.authenticate_bcrypt(username='******',
                                       password='******',
                                       request=None)
    # Verify we are writing to the log file as expected
    with open(log_path, 'r') as log:
        for line in log:
            postrun_loglines += 1
        last_line = line
        log.close()
    last_line_dict = json.loads(last_line)
    assert postrun_loglines > prerun_loglines
    assert last_line_dict['userid'] == user_id
    assert result == (0, 'invalid')
Example #8
0
def signin_post_(request):
    form = request.web_input(username="", password="", referer="", sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password)

    if logid and logerror == 'unicode-failure':
        raise HTTPSeeOther(location='/signin/unicode-failure')
    elif logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logerror == "invalid":
        return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been permanently banned and you are no longer allowed "
            "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
            "contact [email protected] for assistance." % (reason,)))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been temporarily suspended and you are not allowed to "
            "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
            "%s.\n\nIf you believe this suspension is in error, please contact "
            "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release))))
    elif logerror == "address":
        return Response("IP ADDRESS TEMPORARILY BLOCKED")

    return Response(define.errorpage(request.userid))
Example #9
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    from weasyl import login

    # Check that credentials are correct
    logid, logerror = login.authenticate_bcrypt(username, password, session=False)

    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")
        elif login.email_exists(newemail):
            raise WeasylError("emailExists")

    if newpassword:
        if newpassword != newpasscheck:
            raise WeasylError("passwordMismatch")
        elif not login.password_secure(newpassword):
            raise WeasylError("passwordInsecure")

    if newemail:
        d.execute("UPDATE login SET email = '%s' WHERE userid = %i", [newemail, userid])

    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid])
Example #10
0
def test_login_fails_for_invalid_auth_and_logs_failure_if_mod_account(tmpdir, monkeypatch):
    # Required: Monkeypatch the log directory, and the staff.MODS frozenset
    monkeypatch.setenv(macro.MACRO_SYS_LOG_PATH, tmpdir + "/")
    log_path = '%s%s.%s.log' % (macro.MACRO_SYS_LOG_PATH, 'login.fail', d.get_timestamp())
    user_id = db_utils.create_user(username='******', password=raw_password)
    # Set the moderators in libweasyl/staff.py via monkeypatch
    monkeypatch.setattr(staff, 'MODS', frozenset([user_id]))
    # Ensure we are actually writing to the file by counting the file's lines
    prerun_loglines = 0
    # The file might not exist; this is fine; ignore
    try:
        with open(log_path, 'r') as log:
            for line in log:
                prerun_loglines += 1
            log.close()
    except IOError:
        pass
    postrun_loglines = 0
    # Item under test
    result = login.authenticate_bcrypt(username='******', password='******', request=None)
    # Verify we are writing to the log file as expected
    with open(log_path, 'r') as log:
        for line in log:
            postrun_loglines += 1
        last_line = line
        log.close()
    last_line_dict = json.loads(last_line)
    assert postrun_loglines > prerun_loglines
    assert last_line_dict['userid'] == user_id
    assert result == (0, 'invalid')
Example #11
0
def test_login_fails_for_incorrect_credentials():
    random_password = "******"
    db_utils.create_user(password=random_password, username=user_name)
    another_random_password = "******"
    result = login.authenticate_bcrypt(username=user_name,
                                       password=another_random_password)
    assert result == (0, 'invalid')
Example #12
0
    def POST(self):
        form = web.input(username="", password="", referer="", sfwmode="nsfw")
        form.referer = form.referer or '/index'

        logid, logerror = login.authenticate_bcrypt(form.username, form.password)

        if logid and logerror == 'unicode-failure':
            raise web.seeother('/signin/unicode-failure')
        elif logid and logerror is None:
            if form.sfwmode == "sfw":
                web.setcookie("sfwmode", "sfw", 31536000)
            raise web.seeother(form.referer)
        elif logerror == "invalid":
            return define.webpage(self.user_id, template.etc_signin, [True, form.referer])
        elif logerror == "banned":
            reason = moderation.get_ban_reason(logid)
            return define.errorpage(
                self.user_id,
                "Your account has been permanently banned and you are no longer allowed "
                "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
                "contact [email protected] for assistance." % (reason,))
        elif logerror == "suspended":
            suspension = moderation.get_suspension(logid)
            return define.errorpage(
                self.user_id,
                "Your account has been temporarily suspended and you are not allowed to "
                "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
                "%s.\n\nIf you believe this suspension is in error, please contact "
                "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release)))
        elif logerror == "address":
            return "IP ADDRESS TEMPORARILY BLOCKED"

        return define.errorpage(self.user_id)
Example #13
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(
            request.userid,
            "control/2fa/generate_recovery_codes_verify_password.html",
            ["password"],
            title="Generate Recovery Codes: Verify Password"
        ))
    # The user has authenticated, so continue with generating the new recovery codes.
    else:
        # Edge case prevention: Stop the user from having two Weasyl sessions open and trying
        #   to proceed through the generation process with two sets of recovery codes.
        invalidate_other_sessions(request.userid)
        # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent
        #   a user from confusing themselves if they visit the request page twice.
        sess = request.weasyl_session
        gen_rec_codes = True
        if '2fa_recovery_codes_timestamp' in sess.additional_data:
            # Are the codes on the current session < 30 minutes old?
            tstamp = sess.additional_data['2fa_recovery_codes_timestamp']
            if arrow.now().timestamp - tstamp < 1800:
                # We have recent codes on the session, use them instead of generating fresh codes.
                recovery_codes = sess.additional_data['2fa_recovery_codes'].split(',')
                gen_rec_codes = False
        if gen_rec_codes:
            # Either this is a fresh request to generate codes, or the timelimit was exceeded.
            recovery_codes = tfa.generate_recovery_codes()
            _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [
            recovery_codes,
            None
        ], title="Generate Recovery Codes: Save New Recovery Codes"))
Example #14
0
def test_login_fails_if_user_is_banned():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    db_utils.create_banuser(userid=user_id, reason="Testing")
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, 'banned')
Example #15
0
def test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s", id=user_id)
    release_date = d.get_time() + 60
    d.engine.execute("INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
                     id=user_id, reason='test', rel=release_date)
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (user_id, 'suspended')
Example #16
0
def test_verify_success_if_correct_information_provided():
    user_name = 'test'
    user_id = db_utils.create_user(password='******', username=user_name)
    password = '******'
    form = Bag(password=password, passcheck=password)
    resetpassword.force(user_id, form)
    result = login.authenticate_bcrypt(username=user_name, password=password, session=False)
    assert result == (user_id, None)
Example #17
0
def test_login_succeeds_if_suspension_duration_has_expired():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s", id=user_id)
    release_date = d.convert_unixdate(31, 12, 2015)
    d.engine.execute("INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
                     id=user_id, reason='test', rel=release_date)
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (user_id, None)
Example #18
0
def test_logins_with_unicode_failures_succeed_with_corresponding_status():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    # This hash corresponds to "password"
    old_2a_bcrypt_hash = '$2a$04$uOBx2JziJoxjq/F88NjQZ.8mRGE8FgLi3q0Rm3QUlBZnhhInXCb9K'
    d.engine.execute("UPDATE authbcrypt SET hashsum = %(hash)s WHERE userid = %(id)s",
                     hash=old_2a_bcrypt_hash, id=user_id)
    result = login.authenticate_bcrypt(user_name, u"passwordé", request=None)
    assert result == (user_id, 'unicode-failure')
Example #19
0
def test_verify_success_if_correct_information_provided():
    user_name = 'test'
    user_id = db_utils.create_user(password='******', username=user_name)
    password = '******'
    form = Bag(password=password, passcheck=password)
    resetpassword.force(user_id, form)
    result = login.authenticate_bcrypt(username=user_name, password=password, request=None)
    assert result == (user_id, None)
Example #20
0
def test_logins_with_unicode_failures_succeed_with_corresponding_status():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    # This hash corresponds to "password"
    old_2a_bcrypt_hash = '$2a$04$uOBx2JziJoxjq/F88NjQZ.8mRGE8FgLi3q0Rm3QUlBZnhhInXCb9K'
    d.engine.execute("UPDATE authbcrypt SET hashsum = %(hash)s WHERE userid = %(id)s",
                     hash=old_2a_bcrypt_hash, id=user_id)
    result = login.authenticate_bcrypt(user_name, u"passwordé", request=None)
    assert result == (user_id, 'unicode-failure')
Example #21
0
def signin_post_(request):
    form = request.web_input(username="", password="", referer="", sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent)

    if logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logid and logerror == "2fa":
        # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now)
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        index.template_fields.invalidate(logid)
        # Check if out of recovery codes; this should *never* execute normally, save for crafted
        #   webtests. However, check for it and log an error to Sentry if it happens.
        remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid)
        if remaining_recovery_codes == 0:
            raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " +
                               str(logid) + " was zero upon password authentication succeeding, " +
                               "which should be impossible.")
        # Store the authenticated userid & password auth time to the session
        sess = define.get_weasyl_session()
        # The timestamp at which password authentication succeeded
        sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp
        # The userid of the user attempting authentication
        sess.additional_data['2fa_pwd_auth_userid'] = logid
        # The number of times the user has attempted to authenticate via 2FA
        sess.additional_data['2fa_pwd_auth_attempts'] = 0
        sess.save = True
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(logid), form.referer, remaining_recovery_codes, None],
            title="Sign In - 2FA"
        ))
    elif logerror == "invalid":
        return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been permanently banned and you are no longer allowed "
            "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
            "contact %s for assistance." % (reason, MACRO_SUPPORT_ADDRESS)))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been temporarily suspended and you are not allowed to "
            "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
            "%s.\n\nIf you believe this suspension is in error, please contact "
            "%s for assistance." % (suspension.reason, define.convert_date(suspension.release), MACRO_SUPPORT_ADDRESS)))

    raise WeasylError("Unexpected")  # pragma: no cover
Example #22
0
def test_login_succeeds_if_suspension_duration_has_expired():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    release_date = d.convert_unixdate(31, 12, 2015)
    db_utils.create_suspenduser(userid=user_id,
                                reason="Testing",
                                release=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, None)
Example #23
0
def test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    release_date = d.get_time() + 60
    db_utils.create_suspenduser(userid=user_id,
                                reason="Testing",
                                release=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, 'suspended')
Example #24
0
def test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s",
                     id=user_id)
    release_date = d.get_time() + 60
    d.engine.execute(
        "INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
        id=user_id,
        reason='test',
        rel=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, 'suspended')
Example #25
0
def test_login_succeeds_if_suspension_duration_has_expired():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s",
                     id=user_id)
    release_date = d.convert_unixdate(31, 12, 2015)
    d.engine.execute(
        "INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
        id=user_id,
        reason='test',
        rel=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, None)
Example #26
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(request.userid, "control/2fa/init.html", [
            define.get_display_name(request.userid),
            "password"
        ], title="Enable 2FA: Step 1"))
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [
            define.get_display_name(request.userid),
            tfa_secret,
            tfa_qrcode,
            None
        ], title="Enable 2FA: Step 2"))
Example #27
0
def signin_post_(request):
    form = request.web_input(username="",
                             password="",
                             referer="",
                             sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password)

    if logid and logerror == 'unicode-failure':
        raise HTTPSeeOther(location='/signin/unicode-failure')
    elif logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logerror == "invalid":
        return Response(
            define.webpage(request.userid, "etc/signin.html",
                           [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(
            define.errorpage(
                request.userid,
                "Your account has been permanently banned and you are no longer allowed "
                "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
                "contact [email protected] for assistance." % (reason, )))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(
            define.errorpage(
                request.userid,
                "Your account has been temporarily suspended and you are not allowed to "
                "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
                "%s.\n\nIf you believe this suspension is in error, please contact "
                "[email protected] for assistance." %
                (suspension.reason, define.convert_date(suspension.release))))
    elif logerror == "address":
        return Response("IP ADDRESS TEMPORARILY BLOCKED")

    return Response(define.errorpage(request.userid))
Example #28
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(request.userid, "control/2fa/init.html", [
            define.get_display_name(request.userid),
            "password"
        ], title="Enable 2FA: Step 1"))
    # Unlikely that this block will get triggered, but just to be safe, check for it
    elif status == "unicode-failure":
        raise HTTPSeeOther(location='/signin/unicode-failure')
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [
            define.get_display_name(request.userid),
            tfa_secret,
            tfa_qrcode,
            None
        ], title="Enable 2FA: Step 2"))
Example #29
0
    def POST(self):
        form = web.input(username="", password="", referer="", sfwmode="nsfw")
        form.referer = form.referer or '/index'

        logid, logerror = login.authenticate_bcrypt(form.username,
                                                    form.password)

        if logid and logerror == 'unicode-failure':
            raise web.seeother('/signin/unicode-failure')
        elif logid and logerror is None:
            if form.sfwmode == "sfw":
                web.setcookie("sfwmode", "sfw", 31536000)
            raise web.seeother(form.referer)
        elif logerror == "invalid":
            return define.webpage(self.user_id, template.etc_signin,
                                  [True, form.referer])
        elif logerror == "banned":
            reason = moderation.get_ban_reason(logid)
            return define.errorpage(
                self.user_id,
                "Your account has been permanently banned and you are no longer allowed "
                "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
                "contact [email protected] for assistance." % (reason, ))
        elif logerror == "suspended":
            suspension = moderation.get_suspension(logid)
            return define.errorpage(
                self.user_id,
                "Your account has been temporarily suspended and you are not allowed to "
                "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
                "%s.\n\nIf you believe this suspension is in error, please contact "
                "[email protected] for assistance." %
                (suspension.reason, define.convert_date(suspension.release)))
        elif logerror == "address":
            return "IP ADDRESS TEMPORARILY BLOCKED"

        return define.errorpage(self.user_id)
Example #30
0
def authorize_post_(request):
    form = request.web_input(credentials='', username='', password='', remember_me='', mobile='', not_me='')
    try:
        credentials = json.loads(form.credentials)
    except ValueError:
        raise HTTPBadRequest()
    scopes = credentials.pop('scopes')
    error = None
    if form.not_me and form.username:
        userid, error = login.authenticate_bcrypt(form.username, form.password, bool(form.remember_me))
        if error:
            error = errorcode.login_errors.get(error, 'Unknown error.')
    elif not request.userid:
        error = "You must specify a username and password."
    else:
        userid = request.userid
    if error:
        return Response(render_form(request, scopes, credentials, bool(form.mobile), error,
                                    form.username, form.password, bool(form.remember_me),
                                    bool(form.not_me)))
    credentials['userid'] = userid
    response_attrs = server.create_authorization_response(
        *(extract_params(request) + (scopes, credentials)))
    return OAuthResponse(*response_attrs)
Example #31
0
def test_login_fails_if_no_password_provided():
    result = login.authenticate_bcrypt(username=user_name,
                                       password='',
                                       request=None)
    assert result == (0, 'invalid')
Example #32
0
def test_login_succeeds_for_valid_username_and_password():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, None)
Example #33
0
def test_login_fails_if_user_is_banned():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 'b' WHERE userid = %(id)s", id=user_id)
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (user_id, 'banned')
Example #34
0
def signin_post_(request):
    form = request.web_input(username="", password="", referer="", sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent)

    if logid and logerror == 'unicode-failure':
        raise HTTPSeeOther(location='/signin/unicode-failure')
    elif logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logid and logerror == "2fa":
        # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now)
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        index.template_fields.invalidate(logid)
        # Check if out of recovery codes; this should *never* execute normally, save for crafted
        #   webtests. However, check for it and log an error to Sentry if it happens.
        remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid)
        if remaining_recovery_codes == 0:
            raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " +
                               str(logid) + " was zero upon password authentication succeeding, " +
                               "which should be impossible.")
        # Store the authenticated userid & password auth time to the session
        sess = define.get_weasyl_session()
        # The timestamp at which password authentication succeeded
        sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp
        # The userid of the user attempting authentication
        sess.additional_data['2fa_pwd_auth_userid'] = logid
        # The number of times the user has attempted to authenticate via 2FA
        sess.additional_data['2fa_pwd_auth_attempts'] = 0
        sess.save = True
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(logid), form.referer, remaining_recovery_codes, None],
            title="Sign In - 2FA"
        ))
    elif logerror == "invalid":
        return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been permanently banned and you are no longer allowed "
            "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
            "contact [email protected] for assistance." % (reason,)))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been temporarily suspended and you are not allowed to "
            "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
            "%s.\n\nIf you believe this suspension is in error, please contact "
            "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release))))
    elif logerror == "address":
        return Response("IP ADDRESS TEMPORARILY BLOCKED")

    return Response(define.errorpage(request.userid))
Example #35
0
def test_login_fails_for_incorrect_credentials():
    random_password = "******"
    db_utils.create_user(password=random_password, username=user_name)
    another_random_password = "******"
    result = login.authenticate_bcrypt(username=user_name, password=another_random_password, request=None)
    assert result == (0, 'invalid')
Example #36
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    """
    Edit the email address and/or password for a given Weasyl account.

    After verifying the user's current login credentials, edit the user's email address and/or
    password if validity checks pass. If the email is modified, a confirmation email is sent
    to the user's target email with a token which, if used, finalizes the email address change.

    Parameters:
        userid: The `userid` of the Weasyl account to modify.
        username: User-entered username for password-based authentication.
        password: The user's current plaintext password.
        newemail: If changing the email on the account, the new email address. Optional.
        newemailcheck: A verification field for the above to serve as a typo-check. Optional,
        but mandatory if `newemail` provided.
        newpassword: If changing the password, the user's new password. Optional.
        newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if
        `newpassword` provided.
    """
    from weasyl import login

    # Track if any changes were made for later display back to the user.
    changes_made = ""

    # Check that credentials are correct
    logid, logerror = login.authenticate_bcrypt(username, password, request=None)

    # Run checks prior to modifying anything...
    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")

    if newpassword:
        if newpassword != newpasscheck:
            raise WeasylError("passwordMismatch")
        elif not login.password_secure(newpassword):
            raise WeasylError("passwordInsecure")

    # If we are setting a new email, then write the email into a holding table pending confirmation
    #   that the email is valid.
    if newemail:
        # Only actually attempt to change the email if unused; prevent finding out if an email is already registered
        if not login.email_exists(newemail):
            token = security.generate_key(40)
            # Store the current token & email, updating them to overwrite a previous attempt if needed
            d.engine.execute("""
                INSERT INTO emailverify (userid, email, token, createtimestamp)
                VALUES (%(userid)s, %(newemail)s, %(token)s, NOW())
                ON CONFLICT (userid) DO
                  UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW()
            """, userid=userid, newemail=newemail, token=token)

            # Send out the email containing the verification token.
            emailer.append([newemail], None, "Weasyl Email Change Confirmation", d.render("email/verify_emailchange.html", [token, d.get_display_name(userid)]))
        else:
            # The target email exists: let the target know this
            query_username = d.engine.scalar("""
                SELECT login_name FROM login WHERE email = %(email)s
            """, email=newemail)
            emailer.append([newemail], None, "Weasyl Account Information - Duplicate Email on Accounts Rejected", d.render(
                "email/email_in_use_email_change.html", [query_username])
            )

        # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed.
        changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n"

    # If the password is being updated, update the hash, and clear other sessions.
    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid])

        # Invalidate all sessions for `userid` except for the current one
        invalidate_other_sessions(userid)

        # Then add to `changes_made` detailing that the password change has successfully occurred.
        changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions."

    if changes_made != "":
        return changes_made
    else:
        return False
Example #37
0
def test_login_fails_if_incorrect_username_is_provided():
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (0, 'invalid')
Example #38
0
def test_login_fails_if_incorrect_username_is_provided():
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (0, 'invalid')
Example #39
0
def test_login_succeeds_for_valid_username_and_password():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (user_id, None)
Example #40
0
def test_login_fails_if_no_username_provided():
    result = login.authenticate_bcrypt(username='', password='******')
    assert result == (0, 'invalid')
Example #41
0
def test_login_fails_if_no_password_provided():
    result = login.authenticate_bcrypt(username=user_name, password='', request=None)
    assert result == (0, 'invalid')
Example #42
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    """
    Edit the email address and/or password for a given Weasyl account.

    After verifying the user's current login credentials, edit the user's email address and/or
    password if validity checks pass. If the email is modified, a confirmation email is sent
    to the user's target email with a token which, if used, finalizes the email address change.

    Parameters:
        userid: The `userid` of the Weasyl account to modify.
        username: User-entered username for password-based authentication.
        password: The user's current plaintext password.
        newemail: If changing the email on the account, the new email address. Optional.
        newemailcheck: A verification field for the above to serve as a typo-check. Optional,
        but mandatory if `newemail` provided.
        newpassword: If changing the password, the user's new password. Optional.
        newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if
        `newpassword` provided.
    """
    from weasyl import login

    # Track if any changes were made for later display back to the user.
    changes_made = ""

    # Check that credentials are correct
    logid, logerror = login.authenticate_bcrypt(username,
                                                password,
                                                request=None)

    # Run checks prior to modifying anything...
    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")

    if newpassword:
        if newpassword != newpasscheck:
            raise WeasylError("passwordMismatch")
        elif not login.password_secure(newpassword):
            raise WeasylError("passwordInsecure")

    # If we are setting a new email, then write the email into a holding table pending confirmation
    #   that the email is valid.
    if newemail:
        # Only actually attempt to change the email if unused; prevent finding out if an email is already registered
        if not login.email_exists(newemail):
            token = security.generate_key(40)
            # Store the current token & email, updating them to overwrite a previous attempt if needed
            d.engine.execute("""
                INSERT INTO emailverify (userid, email, token, createtimestamp)
                VALUES (%(userid)s, %(newemail)s, %(token)s, NOW())
                ON CONFLICT (userid) DO
                  UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW()
            """,
                             userid=userid,
                             newemail=newemail,
                             token=token)

            # Send out the email containing the verification token.
            emailer.append([newemail], None,
                           "Weasyl Email Change Confirmation",
                           d.render("email/verify_emailchange.html",
                                    [token, d.get_display_name(userid)]))
        else:
            # The target email exists: let the target know this
            query_username = d.engine.scalar("""
                SELECT login_name FROM login WHERE email = %(email)s
            """,
                                             email=newemail)
            emailer.append(
                [newemail], None,
                "Weasyl Account Information - Duplicate Email on Accounts Rejected",
                d.render("email/email_in_use_email_change.html",
                         [query_username]))

        # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed.
        changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n"

    # If the password is being updated, update the hash, and clear other sessions.
    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i",
                  [login.passhash(newpassword), userid])

        # Invalidate all sessions for `userid` except for the current one
        invalidate_other_sessions(userid)

        # Then add to `changes_made` detailing that the password change has successfully occurred.
        changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions."

    if changes_made != "":
        return changes_made
    else:
        return False
Example #43
0
def test_login_fails_if_no_username_provided():
    result = login.authenticate_bcrypt(username='', password='******')
    assert result == (0, 'invalid')