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 exception_catchall(exc, request): expected = isinstance(exc, ExpectedWeasylError) exc_type = type(exc) request.response.status = exc_type.code if expected else 500 if not expected and 'sentry.log_error' in request.environ: request_id = generate_key(8) event_id, = request.environ['sentry.log_error'](request.exc_info, request_id=request_id) else: if not expected: log.error( 'an error occurred, but sentry was not configured to capture it', exc_info=request.exc_info) request_id = event_id = None ret = { 'error': True, 'event_id': event_id, 'request_id': request_id, } if expected: ret.update({ 'message': str(exc), 'code': exc_type.__name__, 'description': exc_type.__doc__, }) return ret
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 create(form): # Normalize form data username = d.plaintext(form.username[:_USERNAME]) sysname = d.get_sysname(username) email = emailer.normalize_address(form.email) emailcheck = emailer.normalize_address(form.emailcheck) password = form.password passcheck = form.passcheck if form.day and form.month and form.year: try: birthday = arrow.Arrow(int(form.year), int(form.month), int(form.day)) except ValueError: raise WeasylError("birthdayInvalid") else: birthday = None # Check mismatched form data if password != passcheck: raise WeasylError("passwordMismatch") if email != emailcheck: raise WeasylError("emailMismatch") # Check invalid form data if birthday is None or d.age_in_years(birthday) < 13: raise WeasylError("birthdayInvalid") if not password_secure(password): raise WeasylError("passwordInsecure") if not email: raise WeasylError("emailInvalid") if not sysname or ";" in username: raise WeasylError("usernameInvalid") if sysname in ["admin", "administrator", "mod", "moderator", "weasyl", "weasyladmin", "weasylmod", "staff", "security"]: raise WeasylError("usernameInvalid") if email_exists(email): raise WeasylError("emailExists") if username_exists(sysname): raise WeasylError("usernameExists") # Create pending account token = security.generate_key(40) d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": email, "birthday": birthday, "unixtime": arrow.now(), }) # Queue verification email emailer.append([email], None, "Weasyl Account Creation", d.render( "email/verify_account.html", [token, sysname])) d.metric('increment', 'createdusers')
def get_temporary(userid, feature): """ Return the full pathname to a temporary file. Temporary files are named so as to be owned by a user. """ return "{root}{temp}{userid}.{feature}.{random}".format(root=PATH_ROOT, temp=PATH_TEMP, userid=userid, feature=feature, random=security.generate_key(20))
def get_token(): from weasyl import api request = get_current_request() if api.is_api_user(request): return '' # allow error pages with $:{TOKEN()} in the template to be rendered even # when the error occurred before the session middleware set a session if not hasattr(request, 'weasyl_session'): return security.generate_key(20) sess = request.weasyl_session if sess.csrf_token is None: sess.csrf_token = security.generate_key(64) sess.save = True return sess.csrf_token
def get_temporary(userid, feature): """ Return the full pathname to a temporary file. Temporary files are named so as to be owned by a user. """ return "{temp}{userid}.{feature}.{random}".format( temp=m.MACRO_SYS_TEMP_PATH, userid=userid, feature=feature, random=security.generate_key(20))
def get_token(): import api if api.is_api_user(): return '' sess = web.ctx.weasyl_session if sess.csrf_token is None: sess.csrf_token = security.generate_key(64) sess.save = True return sess.csrf_token
def get_token(): import api if api.is_api_user(): return '' sess = web.ctx.weasyl_session if sess.csrf_token is None: sess.csrf_token = security.generate_key(64) sess.save = True return sess.csrf_token
def append(db, email, terms): token = security.generate_key(40) email = emailer.normalize_address(email) if not email: raise error.WeasylError("emailInvalid") define.execute(db, "INSERT INTO premiumpurchase VALUES ('%s', '%s', %i)", [token, email, terms]) emailer.append([email], None, "Weasyl Premium Verification", define.render("email/verify_premium.html", [token, terms]))
def get_token(): from weasyl import api if api.is_api_user(): return '' sess = get_current_request().weasyl_session if sess.csrf_token is None: sess.csrf_token = security.generate_key(64) sess.save = True return sess.csrf_token
def get_token(): from weasyl import api if api.is_api_user(): return '' sess = get_current_request().weasyl_session if sess.csrf_token is None: sess.csrf_token = security.generate_key(64) sess.save = True return sess.csrf_token
def __call__(self, environ, start_response): environ['sentry.log_error'] = self.log_error environ['sentry.log_message'] = self.log_message try: return self.app(environ, start_response) except Exception: exc_info = sys.exc_info() request_id = generate_key(8) event_id, = self.log_error(exc_info, request_id=request_id) start_response('500 Internal Server Error', [('Content-Type', 'text/plain')], exc_info) return [('%s-%s' % (event_id, request_id)).encode('utf-8')]
def create_session(user): """ Creates a session for a user and returns the corresponding WZL cookie. """ session = orm.Session() session.sessionid = security.generate_key(64) session.userid = user db = d.connect() db.add(session) db.flush() return 'WZL=' + session.sessionid.encode('utf-8')
def __call__(self, environ, start_response): environ['sentry.log_error'] = self.log_error environ['sentry.log_message'] = self.log_message try: return self.app(environ, start_response) except Exception: exc_info = sys.exc_info() request_id = generate_key(8) event_id, = self.log_error(exc_info, request_id=request_id) start_response( '500 Internal Server Error', [('Content-Type', 'text/plain')], exc_info) return [('%s-%s' % (event_id, request_id)).encode('utf-8')]
def _serialize(self, request, response): if self._invalidated: response.delete_cookie(self._cookie_name) return if not self._changed: return if self._session_obj is None: self._session_obj = Session(sessionid=generate_key(64)) response.set_cookie(self._cookie_name, value=self._session_obj.sessionid, httponly=True) additional_data = self._dict.copy() for k in self._static_fields: setattr(self._session_obj, k, additional_data.pop(k, None)) self._session_obj.additional_data = additional_data request.db.add(self._session_obj) request.db.flush()
def generate_recovery_codes(): """ Generate a set of valid recovery codes. Character set is defined as uppercase ASCII characters (string.ascii_uppercase), plus numerals '123456789'. Numeral zero is excluded due to the confusion potential between '0' and 'O'. Parameters: None Returns: A set of length `_TFA_RECOVERY_CODES` where each code is `LENGTH_RECOVERY_CODE` characters in length. """ # Generate the character-set to use during the generation of the keys. charset = string.ascii_uppercase + "123456789" return [security.generate_key(size=LENGTH_RECOVERY_CODE, key_characters=charset) for i in range(_TFA_RECOVERY_CODES)]
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 _serialize(self, request, response): if self._invalidated: response.delete_cookie(self._cookie_name) return if not self._changed: return if self._session_obj is None: self._session_obj = Session(sessionid=generate_key(64)) response.set_cookie(self._cookie_name, value=self._session_obj.sessionid, httponly=True) additional_data = self._dict.copy() for k in self._static_fields: setattr(self._session_obj, k, additional_data.pop(k, None)) self._session_obj.additional_data = additional_data request.db.add(self._session_obj) request.db.flush()
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 request(email): token = security.generate_key(25, key_characters=string.digits + string.ascii_lowercase) token_sha256 = _hash_token(token) email = emailer.normalize_address(email) if email is None: raise WeasylError("emailInvalid") d.engine.execute( "INSERT INTO forgotpassword (email, token_sha256)" " VALUES (%(email)s, %(token_sha256)s)", email=email, token_sha256=bytearray(token_sha256)) # Generate and send an email to the user containing a password reset link emailer.send(email, "Weasyl Account Recovery", d.render("email/reset_password.html", [token]))
def session_tween(request): cookies_to_clear = set() if 'beaker.session.id' in request.cookies: cookies_to_clear.add('beaker.session.id') session = d.connect() sess_obj = None if 'WZL' in request.cookies: sess_obj = session.query(orm.Session).get(request.cookies['WZL']) if sess_obj is None: # clear an invalid session cookie if nothing ends up trying to create a # new one cookies_to_clear.add('WZL') if sess_obj is None: sess_obj = orm.Session() sess_obj.create = True sess_obj.sessionid = security.generate_key(64) # BUG: Because of the way our exception handler relies on a weasyl_session, exceptions # thrown before this part will not be handled correctly. request.weasyl_session = sess_obj # Register a response callback to clear and set the session cookies before returning. # Note that this requires that exceptions are handled properly by our exception view. def callback(request, response): if sess_obj.save: session.begin() if sess_obj.create: session.add(sess_obj) response.set_cookie('WZL', sess_obj.sessionid, max_age=60 * 60 * 24 * 365, secure=request.scheme == 'https', httponly=True) # don't try to clear the cookie if we're saving it cookies_to_clear.discard('WZL') session.commit() for name in cookies_to_clear: response.delete_cookie(name) request.add_response_callback(callback) return handler(request)
def generate_recovery_codes(): """ Generate a set of valid recovery codes. Character set is defined as uppercase ASCII characters (string.ascii_uppercase), plus numerals '123456789'. Numeral zero is excluded due to the confusion potential between '0' and 'O'. Parameters: None Returns: A set of length `_TFA_RECOVERY_CODES` where each code is `LENGTH_RECOVERY_CODE` characters in length. """ # Generate the character-set to use during the generation of the keys. charset = string.ascii_uppercase + "123456789" return [ security.generate_key(size=LENGTH_RECOVERY_CODE, key_characters=charset) for i in range(_TFA_RECOVERY_CODES) ]
def session_processor(handle): cookies = web.cookies() cookies_to_clear = set() if 'beaker.session.id' in cookies: cookies_to_clear.add('beaker.session.id') session = d.connect() sess_obj = None if 'WZL' in cookies: sess_obj = session.query(orm.Session).get(cookies['WZL']) if sess_obj is None: # clear an invalid session cookie if nothing ends up trying to create a # new one cookies_to_clear.add('WZL') if sess_obj is None: sess_obj = orm.Session() sess_obj.create = True sess_obj.sessionid = security.generate_key(64) web.ctx.weasyl_session = sess_obj try: return handle() finally: if sess_obj.save: session.begin() if sess_obj.create: session.add(sess_obj) web.setcookie('WZL', sess_obj.sessionid, expires=60 * 60 * 24 * 365, secure=web.ctx.protocol == 'https', httponly=True) # don't try to clear the cookie if we're saving it cookies_to_clear.discard('WZL') session.commit() for name in cookies_to_clear: # this sets the cookie to expire one second before the HTTP request was # issued, which is the closest you can get to 'clearing' a cookie. web.setcookie(name, '', expires=-1)
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 session_tween(request): cookies_to_clear = set() if 'beaker.session.id' in request.cookies: cookies_to_clear.add('beaker.session.id') session = d.connect() sess_obj = None if 'WZL' in request.cookies: sess_obj = session.query(orm.Session).get(request.cookies['WZL']) if sess_obj is None: # clear an invalid session cookie if nothing ends up trying to create a # new one cookies_to_clear.add('WZL') if sess_obj is None: sess_obj = orm.Session() sess_obj.create = True sess_obj.sessionid = security.generate_key(64) # BUG: Because of the way our exception handler relies on a weasyl_session, exceptions # thrown before this part will not be handled correctly. request.weasyl_session = sess_obj # Register a response callback to clear and set the session cookies before returning. # Note that this requires that exceptions are handled properly by our exception view. def callback(request, response): if sess_obj.save: session.begin() if sess_obj.create: session.add(sess_obj) response.set_cookie('WZL', sess_obj.sessionid, max_age=60 * 60 * 24 * 365, secure=request.scheme == 'https', httponly=True) # don't try to clear the cookie if we're saving it cookies_to_clear.discard('WZL') session.commit() for name in cookies_to_clear: response.delete_cookie(name) request.add_response_callback(callback) return handler(request)
def session_processor(handle): cookies = web.cookies() cookies_to_clear = set() if 'beaker.session.id' in cookies: cookies_to_clear.add('beaker.session.id') session = d.connect() sess_obj = None if 'WZL' in cookies: sess_obj = session.query(orm.Session).get(cookies['WZL']) if sess_obj is None: # clear an invalid session cookie if nothing ends up trying to create a # new one cookies_to_clear.add('WZL') if sess_obj is None: sess_obj = orm.Session() sess_obj.create = True sess_obj.sessionid = security.generate_key(64) web.ctx.weasyl_session = sess_obj try: return handle() finally: if sess_obj.save: session.begin() if sess_obj.create: session.add(sess_obj) web.setcookie('WZL', sess_obj.sessionid, expires=60 * 60 * 24 * 365, secure=web.ctx.protocol == 'https', httponly=True) # don't try to clear the cookie if we're saving it cookies_to_clear.discard('WZL') session.commit() for name in cookies_to_clear: # this sets the cookie to expire one second before the HTTP request was # issued, which is the closest you can get to 'clearing' a cookie. web.setcookie(name, '', expires=-1)
def create_guest_session(): token = security.generate_key(GUEST_TOKEN_LENGTH) sess = GuestSession(token) sess.create = True return sess
def new_csrf_token(self): ret = self['csrf_token'] = generate_key(64) return ret
def add_api_key(userid, description): db = Base.dbsession() db.add(APIToken(userid=userid, token=security.generate_key(48), description=description)) db.flush()
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
def create_session(userid): sess = Session(userid=userid) sess.sessionid = security.generate_key(USER_TOKEN_LENGTH) sess.create = True sess.save = True return sess
def create_guest_session(): token = security.generate_key(GUEST_TOKEN_LENGTH) sess = GuestSession(token) sess.create = True return sess
def new_csrf_token(self): ret = self['csrf_token'] = generate_key(64) return ret
def create(form): # Normalize form data username = d.plaintext(form.username[:_USERNAME]) sysname = d.get_sysname(username) email = emailer.normalize_address(form.email) emailcheck = emailer.normalize_address(form.emailcheck) password = form.password passcheck = form.passcheck if form.day and form.month and form.year: try: birthday = arrow.Arrow(int(form.year), int(form.month), int(form.day)) except ValueError: raise WeasylError("birthdayInvalid") else: birthday = None # Check mismatched form data if password != passcheck: raise WeasylError("passwordMismatch") if email != emailcheck: raise WeasylError("emailMismatch") # Check invalid form data if birthday is None or d.age_in_years(birthday) < 13: raise WeasylError("birthdayInvalid") if not password_secure(password): raise WeasylError("passwordInsecure") if not email: raise WeasylError("emailInvalid") if not sysname or ";" in username: raise WeasylError("usernameInvalid") if sysname in [ "admin", "administrator", "mod", "moderator", "weasyl", "weasyladmin", "weasylmod", "staff", "security" ]: raise WeasylError("usernameInvalid") if email_exists(email): raise WeasylError("emailExists") if username_exists(sysname): raise WeasylError("usernameExists") # Create pending account token = security.generate_key(40) d.engine.execute( d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": email, "birthday": birthday, "unixtime": arrow.now(), }) # Queue verification email emailer.append([email], None, "Weasyl Account Creation", d.render("email/verify_account.html", [token, sysname])) d.metric('increment', 'createdusers')
def create(form): # Normalize form data username = d.plaintext(form.username[:_USERNAME]) sysname = d.get_sysname(username) email = emailer.normalize_address(form.email) emailcheck = emailer.normalize_address(form.emailcheck) password = form.password passcheck = form.passcheck if form.day and form.month and form.year: try: birthday = arrow.Arrow(int(form.year), int(form.month), int(form.day)) except ValueError: raise WeasylError("birthdayInvalid") else: birthday = None # Check mismatched form data if password != passcheck: raise WeasylError("passwordMismatch") if email != emailcheck: raise WeasylError("emailMismatch") # Check invalid form data if birthday is None or d.age_in_years(birthday) < 13: raise WeasylError("birthdayInvalid") if not password_secure(password): raise WeasylError("passwordInsecure") if not email: raise WeasylError("emailInvalid") if is_email_blacklisted(email): raise WeasylError("emailBlacklisted") if not sysname or ";" in username: raise WeasylError("usernameInvalid") if sysname in ["admin", "administrator", "mod", "moderator", "weasyl", "weasyladmin", "weasylmod", "staff", "security"]: raise WeasylError("usernameInvalid") if username_exists(sysname): raise WeasylError("usernameExists") # Account verification token token = security.generate_key(40) # Only attempt to create the account if the email is unused (as defined by the function) if not email_exists(email): # Create pending account d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": email, "birthday": birthday, "unixtime": arrow.now(), }) # Queue verification email emailer.append([email], None, "Weasyl Account Creation", d.render( "email/verify_account.html", [token, sysname])) d.metric('increment', 'createdusers') else: # Store a dummy record to support plausible deniability of email addresses # So "reserve" the username, but mark the record invalid, and use the token to satisfy the uniqueness # constraint for the email field (e.g., if there is already a valid, pending row in the table). d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": token, "birthday": arrow.now(), "unixtime": arrow.now(), "invalid": True, }) # The email address in question is already in use in either `login` or `logincreate`; # let the already registered user know this via email (perhaps they forgot their username/password) query_username_login = d.engine.scalar("SELECT login_name FROM login WHERE email = %(email)s", email=email) query_username_logincreate = d.engine.scalar("SELECT login_name FROM logincreate WHERE email = %(email)s", email=email) emailer.append([email], None, "Weasyl Account Creation - Account Already Exists", d.render( "email/email_in_use_account_creation.html", [query_username_login or query_username_logincreate]))
def test_generate_key(self): length = 20 self.assertEqual(length, len(security.generate_key(length)))
def create_session(userid): sess = Session(userid=userid) sess.sessionid = security.generate_key(USER_TOKEN_LENGTH) sess.create = True sess.save = True return sess
def create(form): # Normalize form data username = clean_display_name(form.username) sysname = d.get_sysname(username) email = emailer.normalize_address(form.email) emailcheck = emailer.normalize_address(form.emailcheck) password = form.password passcheck = form.passcheck if form.day and form.month and form.year: try: birthday = arrow.Arrow(int(form.year), int(form.month), int(form.day)) except ValueError: raise WeasylError("birthdayInvalid") else: birthday = None # Check mismatched form data if password != passcheck: raise WeasylError("passwordMismatch") if email != emailcheck: raise WeasylError("emailMismatch") # Check invalid form data if birthday is None or d.age_in_years(birthday) < 13: raise WeasylError("birthdayInvalid") if not password_secure(password): raise WeasylError("passwordInsecure") if not email: raise WeasylError("emailInvalid") if is_email_blacklisted(email): raise WeasylError("emailBlacklisted") if username_exists(sysname): raise WeasylError("usernameExists") # Account verification token token = security.generate_key(40) # Only attempt to create the account if the email is unused (as defined by the function) if not email_exists(email): # Create pending account d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": email, "birthday": birthday, }) # Send verification email emailer.send(email, "Weasyl Account Creation", d.render( "email/verify_account.html", [token, sysname])) d.metric('increment', 'createdusers') else: # Store a dummy record to support plausible deniability of email addresses # So "reserve" the username, but mark the record invalid, and use the token to satisfy the uniqueness # constraint for the email field (e.g., if there is already a valid, pending row in the table). d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": token, "birthday": arrow.now(), "invalid": True, # So we have a way for admins to determine which email address collided in the View Pending Accounts Page "invalid_email_addr": email, }) # The email address in question is already in use in either `login` or `logincreate`; # let the already registered user know this via email (perhaps they forgot their username/password) query_username_login = d.engine.scalar("SELECT login_name FROM login WHERE email = %(email)s", email=email) query_username_logincreate = d.engine.scalar("SELECT login_name FROM logincreate WHERE email = %(email)s", email=email) emailer.send(email, "Weasyl Account Creation - Account Already Exists", d.render( "email/email_in_use_account_creation.html", [query_username_login or query_username_logincreate]))
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