def ravenCaptureArguments(self, level=None, **extra): request = get_current_request() data = { 'level': level, 'user': { 'id': d.get_userid(), 'ip_address': d.get_address(), }, 'request': { 'url': request.environ['PATH_INFO'], 'method': request.environ['REQUEST_METHOD'], 'data': request.POST, 'query_string': request.environ['QUERY_STRING'], 'headers': http.get_headers(request.environ), 'env': request.environ, }, } return { 'data': data, 'extra': dict( extra, session=getattr(request, 'weasyl_session', None), ), }
def request(form): token = security.generate_key(100) email = emailer.normalize_address(form.email) # Determine the user associated with `username`; if the user is not found, # raise an exception user_id = d.engine.scalar(""" SELECT userid FROM login WHERE email = %(email)s """, email=email) # If `user_id` exists, then the supplied email was valid; if not valid, do nothing, raising # no errors for plausible deniability of email existence if user_id: # Insert a record into the forgotpassword table for the user, # or update an existing one now = d.get_time() address = d.get_address() d.engine.execute(""" INSERT INTO forgotpassword (userid, token, set_time, address) VALUES (%(id)s, %(token)s, %(time)s, %(address)s) ON CONFLICT (userid) DO UPDATE SET token = %(token)s, set_time = %(time)s, address = %(address)s """, id=user_id, token=token, time=now, address=address) # Generate and send an email to the user containing a password reset link emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
def request(form): token = security.generate_key(100) email = emailer.normalize_address(form.email) username = d.get_sysname(form.username) # Determine the user associated with `username`; if the user is not found, # raise an exception user = d.engine.execute( "SELECT userid, email FROM login WHERE login_name = %(username)s", username=username).first() if not user: raise WeasylError("loginRecordMissing") # Check the user's email address against the provided e-mail address, # raising an exception if there is a mismatch if email != emailer.normalize_address(user.email): raise WeasylError("emailInvalid") # Insert a record into the forgotpassword table for the user, # or update an existing one now = d.get_time() address = d.get_address() d.engine.execute(""" INSERT INTO forgotpassword (userid, token, set_time, address) VALUES (%(id)s, %(token)s, %(time)s, %(address)s) ON CONFLICT (userid) DO UPDATE SET token = %(token)s, set_time = %(time)s, address = %(address)s """, id=user.userid, token=token, time=now, address=address) # Generate and send an email to the user containing a password reset link emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
def test_true_returned_if_token_exists(): user_id = db_utils.create_user(username='******') token = "testtokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentest000001" d.engine.execute(d.meta.tables["forgotpassword"].insert(), { "userid": user_id, "token": token, "set_time": d.get_time(), "link_time": 0, "address": d.get_address(), }) assert resetpassword.prepare(token)
def test_true_returned_if_token_exists(): user_id = db_utils.create_user(username='******') token = "testtokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentest000001" d.engine.execute(d.meta.tables["forgotpassword"].insert(), { "userid": user_id, "token": token, "set_time": d.get_time(), "link_time": 0, "address": d.get_address(), }) assert resetpassword.checktoken(token)
def signin(userid): # Update the last login record for the user d.execute("UPDATE login SET last_login = %i WHERE userid = %i", [d.get_time(), userid]) # Log the successful login and increment the login count d.append_to_log('login.success', userid=userid, ip=d.get_address()) d.metric('increment', 'logins') # set the userid on the session sess = d.get_weasyl_session() sess.userid = userid sess.save = True
def reset(form): from weasyl import login # Raise an exception if `password` does not enter `passcheck` (indicating # that the user mistyped one of the fields) or if `password` does not meet # the system's password security requirements if form.password != form.passcheck: raise WeasylError("passwordMismatch") elif not login.password_secure(form.password): raise WeasylError("passwordInsecure") # Select the user information and record data from the forgotpassword table # pertaining to `token`, requiring that the link associated with the record # be visited no more than five minutes prior; if the forgotpassword record is # not found or does not meet this requirement, raise an exception query = d.engine.execute(""" SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address FROM login lo INNER JOIN userinfo ui USING (userid) INNER JOIN forgotpassword fp USING (userid) WHERE fp.token = %(token)s AND fp.link_time > %(cutoff)s """, token=form.token, cutoff=d.get_time() - 300).first() if not query: raise WeasylError("forgotpasswordRecordMissing") USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query # Check `username` and `email` against known correct values and raise an # exception if there is a mismatch if emailer.normalize_address( form.email) != emailer.normalize_address(EMAIL): raise WeasylError("emailIncorrect") elif d.get_sysname(form.username) != USERNAME: raise WeasylError("usernameIncorrect") elif d.get_address() != ADDRESS: raise WeasylError("addressInvalid") # Update the authbcrypt table with a new password hash d.engine.execute( 'INSERT INTO authbcrypt (userid, hashsum) VALUES (%(user)s, %(hash)s) ' 'ON CONFLICT (userid) DO UPDATE SET hashsum = %(hash)s', user=USERID, hash=login.passhash(form.password)) d.engine.execute("DELETE FROM forgotpassword WHERE token = %(token)s", token=form.token)
def reset(form): import login # Raise an exception if `password` does not enter `passcheck` (indicating # that the user mistyped one of the fields) or if `password` does not meet # the system's password security requirements if form.password != form.passcheck: raise WeasylError("passwordMismatch") elif not login.password_secure(form.password): raise WeasylError("passwordInsecure") # Select the user information and record data from the forgotpassword table # pertaining to `token`, requiring that the link associated with the record # be visited no more than five minutes prior; if the forgotpassword record is # not found or does not meet this requirement, raise an exception query = d.execute(""" SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address FROM login lo INNER JOIN userinfo ui USING (userid) INNER JOIN forgotpassword fp USING (userid) WHERE fp.token = '%s' AND fp.link_time > %i """, [form.token, d.get_time() - 300], options="single") if not query: raise WeasylError("forgotpasswordRecordMissing") USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query # Check `username` and `email` against known correct values and raise an # exception if there is a mismatch if emailer.normalize_address(form.email) != emailer.normalize_address(EMAIL): raise WeasylError("emailIncorrect") elif d.get_sysname(form.username) != USERNAME: raise WeasylError("usernameIncorrect") elif d.get_address() != ADDRESS: raise WeasylError("addressInvalid") # Update the authbcrypt table with a new password hash """ TODO TEMPORARY """ try: d.execute("INSERT INTO authbcrypt VALUES (%i, '')", [USERID]) except: pass d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(form.password), USERID]) d.execute("DELETE FROM forgotpassword WHERE token = '%s'", [form.token])
def request(form): token = security.generate_key(100) email = emailer.normalize_address(form.email) username = d.get_sysname(form.username) # Determine the user associated with `username`; if the user is not found, # raise an exception user = d.engine.execute( "SELECT userid, email FROM login WHERE login_name = %(username)s", username=username).first() if not user: raise WeasylError("loginRecordMissing") # Check the user's email address against the provided e-mail address, # raising an exception if there is a mismatch if email != emailer.normalize_address(user.email): raise WeasylError("emailInvalid") # Insert a record into the forgotpassword table for the user, # or update an existing one now = d.get_time() address = d.get_address() try: d.engine.execute( "INSERT INTO forgotpassword (userid, token, set_time, address)" " VALUES (%(id)s, %(token)s, %(time)s, %(address)s)", id=user.userid, token=token, time=now, address=address) except IntegrityError: # An IntegrityError will probably indicate that a password reset request # already exists and that the existing row should be updated. If the update # doesn't find anything, though, the original error should be re-raised. result = d.engine.execute(""" UPDATE forgotpassword SET token = %(token)s, set_time = %(time)s, address = %(address)s WHERE userid = %(id)s """, id=user.userid, token=token, time=now, address=address) if result.rowcount != 1: raise # Generate and send an email to the user containing a password reset link emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
def signin(request, userid, ip_address=None, user_agent=None): # Update the last login record for the user d.execute("UPDATE login SET last_login = NOW() WHERE userid = %i", [userid]) # Log the successful login and increment the login count d.append_to_log('login.success', userid=userid, ip=d.get_address()) d.metric('increment', 'logins') # set the userid on the session sess = create_session(userid) sess.ip_address = ip_address sess.user_agent_id = get_user_agent_id(user_agent) sess.create = True if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = sess
def signin(request, userid, ip_address=None, user_agent=None): # Update the last login record for the user d.execute("UPDATE login SET last_login = %i WHERE userid = %i", [d.get_time(), userid]) # Log the successful login and increment the login count d.append_to_log('login.success', userid=userid, ip=d.get_address()) d.metric('increment', 'logins') # set the userid on the session sess = create_session(userid) sess.ip_address = ip_address sess.user_agent_id = get_user_agent_id(user_agent) sess.create = True if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = sess
def ravenCaptureArguments(self, level=None, **extra): data = { 'level': level, 'user': { 'id': d.get_userid(), 'ip_address': d.get_address(), }, 'request': { 'url': web.ctx.env['PATH_INFO'], 'method': web.ctx.env['REQUEST_METHOD'], 'data': web.webapi.rawinput(method='POST'), 'query_string': web.ctx.env['QUERY_STRING'], 'headers': http.get_headers(web.ctx.env), 'env': web.ctx.env, }, } return { 'data': data, 'extra': dict( extra, session=web.ctx.get('weasyl_session'), ), }
def authenticate_bcrypt(username, password, request, ip_address=None, user_agent=None): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass None as the `request` to authenticate a user without creating a new session. :param username: The username of the user attempting authentication. :param password: The user's claimed password to check against the stored hash. :param request: The request, or None :param ip_address: The address requesting authentication. :param user_agent: The user agent string of the submitting client. Possible errors are: - "invalid" - "unexpected" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.engine.execute( "SELECT ab.userid, ab.hashsum, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = %(name)s", name=d.get_sysname(username), ).first() if not query: return 0, "invalid" USERID, HASHSUM, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') _, IS_BANNED, IS_SUSPENDED = d.get_login_settings(USERID) d.metric('increment', 'attemptedlogins') if not bcrypt.checkpw(password.encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif IS_BANNED: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif IS_SUSPENDED: from weasyl import moderation suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d._get_all_config.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if this is a request to log in, then log the signin # if it succeeded. if request is not None: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = create_session(None) request.weasyl_session.additional_data = {} return USERID, "2fa" else: signin(request, USERID, ip_address=ip_address, user_agent=user_agent) # Either way, authentication succeeded, so return the userid and a status. return USERID, None
def authenticate_bcrypt(username, password, session=True): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass `session` as False to authenticate a user without creating a new session. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute("SELECT ab.userid, ab.hashsum, lo.settings FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw(d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if `session` is True, then log the signin # if it succeeded. if session: signin(USERID) d.append_to_log('login.success', userid=USERID, ip=d.get_address()) d.metric('increment', 'logins') status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status
def authenticate_bcrypt(username, password, session=True): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass `session` as False to authenticate a user without creating a new session. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute( "SELECT ab.userid, ab.hashsum, lo.settings, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw( d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute( "UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if `session` is True, then log the signin # if it succeeded. if session: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: return USERID, "2fa" else: signin(USERID) status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status
def authenticate_bcrypt(username, password, request, ip_address=None, user_agent=None): """ Return a result tuple of the form (userid, error); `error` is None if the login was successful. Pass None as the `request` to authenticate a user without creating a new session. :param username: The username of the user attempting authentication. :param password: The user's claimed password to check against the stored hash. :param ip_address: The address requesting authentication. :param user_agent: The user agent string of the submitting client. Possible errors are: - "invalid" - "unexpected" - "address" - "banned" - "suspended" - "2fa" - Indicates the user has opted-in to 2FA. Additional authentication required. """ # Check that the user entered potentially valid values for `username` and # `password` before attempting to authenticate them if not username or not password: return 0, "invalid" # Select the authentication data necessary to check that the the user-entered # credentials are valid query = d.execute("SELECT ab.userid, ab.hashsum, lo.settings, lo.twofa_secret FROM authbcrypt ab" " RIGHT JOIN login lo USING (userid)" " WHERE lo.login_name = '%s'", [d.get_sysname(username)], ["single"]) if not query: return 0, "invalid" USERID, HASHSUM, SETTINGS, TWOFA = query HASHSUM = HASHSUM.encode('utf-8') d.metric('increment', 'attemptedlogins') unicode_success = bcrypt.checkpw(password.encode('utf-8'), HASHSUM) if not unicode_success and not bcrypt.checkpw(d.plaintext(password).encode('utf-8'), HASHSUM): # Log the failed login attempt in a security log if the account the user # attempted to log into is a privileged account if USERID in staff.MODS: d.append_to_log('login.fail', userid=USERID, ip=d.get_address()) d.metric('increment', 'failedlogins') # Return a zero userid and an error code (indicating the entered password # was incorrect) return 0, "invalid" elif "b" in SETTINGS: # Return the proper userid and an error code (indicating the user's account # has been banned) return USERID, "banned" elif "s" in SETTINGS: suspension = moderation.get_suspension(USERID) if d.get_time() > suspension.release: d.execute("UPDATE login SET settings = REPLACE(settings, 's', '') WHERE userid = %i", [USERID]) d.execute("DELETE FROM suspension WHERE userid = %i", [USERID]) d.get_login_settings.invalidate(USERID) else: # Return the proper userid and an error code (indicating the user's # account has been temporarily suspended) return USERID, "suspended" # Attempt to create a new session if this is a request to log in, then log the signin # if it succeeded. if request is not None: # If the user's record has ``login.twofa_secret`` set (not nulled), return that password authentication succeeded. if TWOFA: if not isinstance(request.weasyl_session, GuestSession): request.pg_connection.delete(request.weasyl_session) request.pg_connection.flush() request.weasyl_session = create_session(None) request.weasyl_session.additional_data = {} return USERID, "2fa" else: signin(request, USERID, ip_address=ip_address, user_agent=user_agent) status = None if not unicode_success: # Oops; the user's password was stored badly, but they did successfully authenticate. status = 'unicode-failure' # Either way, authentication succeeded, so return the userid and a status. return USERID, status