def _targetid(link): """ Returns the content service's identifier based on the URL provided. """ service = _service(link) if service in _OEMBED_MAP: # Validate with oEmbed link = link.strip() alpha = string.printable elif service == "bandcamp": content = d.http_get(link).text match = _BANDCAMP_EMBED.search(content) if match: return match.groups() else: return None else: return None for i in range(len(link)): if link[i] not in alpha: return link[:i] return d.plaintext(link)
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 update_unicode_password(userid, password, password_confirm): if password != password_confirm: raise WeasylError('passwordMismatch') if not password_secure(password): raise WeasylError('passwordInsecure') hashpw = d.engine.scalar(""" SELECT hashsum FROM authbcrypt WHERE userid = %(userid)s """, userid=userid).encode('utf-8') if bcrypt.checkpw(password.encode('utf-8'), hashpw): return if not bcrypt.checkpw(d.plaintext(password).encode('utf-8'), hashpw): raise WeasylError('passwordIncorrect') d.engine.execute(""" UPDATE authbcrypt SET hashsum = %(hashsum)s WHERE userid = %(userid)s """, userid=userid, hashsum=passhash(password))
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 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 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 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
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')