def send(mailto, subject, content): """Send an e-mail. `mailto` must be a normalized e-mail address to send this e-mail to. The system email will be designated as the sender. """ message = MIMEText(content.strip()) message["To"] = mailto message["From"] = macro.MACRO_EMAIL_ADDRESS message["Subject"] = subject # smtp.sendmail() only converts CR and LF (produced by MIMEText and our templates) to CRLF in Python 3. In Python 2, we need this: msg_crlf = re.sub(r"\r\n|[\r\n]", "\r\n", message.as_string()) smtp = SMTP(define.config_read_setting('host', "localhost", section='smtp')) try: smtp.sendmail( from_addr=macro.MACRO_EMAIL_ADDRESS, to_addrs=[mailto], msg=msg_crlf, ) finally: smtp.quit() define.metric('increment', 'emails')
def insert(userid, target_user, parentid, content, staffnotes): # Check invalid content if not content: raise WeasylError("commentInvalid") elif not target_user or not d.is_vouched_for(target_user): raise WeasylError("Unexpected") # Determine parent userid if parentid: parentuserid = d.engine.scalar( "SELECT userid FROM comments WHERE commentid = %(parent)s", parent=parentid, ) if parentuserid is None: raise WeasylError("shoutRecordMissing") else: parentuserid = None # Check permissions if userid not in staff.MODS: if ignoreuser.check(target_user, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, target_user): raise WeasylError("youIgnoredPageOwner") elif ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") _, is_banned, _ = d.get_login_settings(target_user) profile_config = d.get_config(target_user) if is_banned or "w" in profile_config or "x" in profile_config and not frienduser.check( userid, target_user): raise WeasylError("insufficientActionPermissions") # Create comment settings = 's' if staffnotes else '' co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar(co.insert().values(userid=userid, target_user=target_user, parentid=parentid or None, content=content, unixtime=arrow.utcnow(), settings=settings).returning( co.c.commentid)) # Create notification if parentid and userid != parentuserid: if not staffnotes or parentuserid in staff.MODS: welcome.shoutreply_insert(userid, commentid, parentuserid, parentid, staffnotes) elif not staffnotes and target_user and userid != target_user: welcome.shout_insert(userid, commentid, otherid=target_user) d.metric('increment', 'shouts') return commentid
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 insert(userid, shout, staffnotes=False): # Check invalid content if not shout.content: raise WeasylError("commentInvalid") elif not shout.userid: raise WeasylError("Unexpected") # Determine indent and parentuserid if shout.parentid: query = d.execute("SELECT userid, indent FROM comments WHERE commentid = %i", [shout.parentid], options="single") if not query: raise WeasylError("shoutRecordMissing") indent, parentuserid = query[1] + 1, query[0] else: indent, parentuserid = 0, None # Check permissions if userid not in staff.MODS: if ignoreuser.check(shout.userid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, shout.userid): raise WeasylError("youIgnoredPageOwner") elif ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") settings = d.execute("SELECT lo.settings, pr.config FROM login lo" " INNER JOIN profile pr ON lo.userid = pr.userid" " WHERE lo.userid = %i", [shout.userid], options="single") if "b" in settings[0] or "w" in settings[1] or "x" in settings[1] and not frienduser.check(userid, shout.userid): raise WeasylError("insufficientActionPermissions") # Create comment settings = 's' if staffnotes else '' co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar( co.insert() .values(userid=userid, target_user=shout.userid, parentid=shout.parentid or None, content=shout.content, unixtime=arrow.utcnow(), indent=indent, settings=settings) .returning(co.c.commentid)) # Create notification if shout.parentid and userid != parentuserid: if not staffnotes or parentuserid in staff.MODS: welcome.shoutreply_insert(userid, commentid, parentuserid, shout.parentid, staffnotes) elif not staffnotes and shout.userid and userid != shout.userid: welcome.shout_insert(userid, commentid, otherid=shout.userid) d.metric('increment', 'shouts') return commentid
def insert(userid, shout, staffnotes=False): # Check invalid content if not shout.content: raise WeasylError("commentInvalid") elif not shout.userid: raise WeasylError("Unexpected") # Determine indent and parentuserid if shout.parentid: query = d.execute("SELECT userid, indent FROM comments WHERE commentid = %i", [shout.parentid], options="single") if not query: raise WeasylError("shoutRecordMissing") indent, parentuserid = query[1] + 1, query[0] else: indent, parentuserid = 0, None # Check permissions if userid not in staff.MODS: if ignoreuser.check(shout.userid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, shout.userid): raise WeasylError("youIgnoredPageOwner") elif ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") settings = d.execute("SELECT lo.settings, pr.config FROM login lo" " INNER JOIN profile pr ON lo.userid = pr.userid" " WHERE lo.userid = %i", [shout.userid], options="single") if "b" in settings[0] or "w" in settings[1] or "x" in settings[1] and not frienduser.check(userid, shout.userid): raise WeasylError("insufficientActionPermissions") # Create comment settings = 's' if staffnotes else '' co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar( co.insert() .values(userid=userid, target_user=shout.userid, parentid=shout.parentid or None, content=shout.content, unixtime=arrow.utcnow(), indent=indent, settings=settings) .returning(co.c.commentid)) # Create notification if shout.parentid and userid != parentuserid: if not staffnotes or parentuserid in staff.MODS: welcome.shoutreply_insert(userid, commentid, parentuserid, shout.parentid, staffnotes) elif not staffnotes and shout.userid and userid != shout.userid: welcome.shout_insert(userid, commentid, otherid=shout.userid) d.metric('increment', 'shouts') return commentid
def create(userid, journal, friends_only=False, tags=None): # Check invalid arguments if not journal.title: raise WeasylError("titleInvalid") elif not journal.content: raise WeasylError("contentInvalid") elif not journal.rating: raise WeasylError("ratingInvalid") profile.check_user_rating_allowed(userid, journal.rating) # Assign settings settings = "f" if friends_only else "" # Create journal jo = d.meta.tables["journal"] # Run the journal through Akismet to check for spam is_spam = False if spam_filtering.FILTERING_ENABLED: result = spam_filtering.check( user_ip=journal.submitter_ip_address, user_agent_id=journal.submitter_user_agent_id, user_id=userid, comment_type="journal", comment_content=journal.content, ) if result == SpamStatus.DefiniteSpam: raise WeasylError("SpamFilteringDropped") elif result == SpamStatus.ProbableSpam: is_spam = True journalid = d.engine.scalar(jo.insert().returning(jo.c.journalid), { "userid": userid, "title": journal.title, "content": journal.content, "rating": journal.rating.code, "unixtime": arrow.now(), "settings": settings, "is_spam": is_spam, "submitter_ip_address": journal.submitter_ip_address, "submitter_user_agent_id": journal.submitter_user_agent_id, }) # Assign search tags searchtag.associate(userid, tags, journalid=journalid) # Create notifications if "m" not in settings: welcome.journal_insert(userid, journalid, rating=journal.rating.code, settings=settings) d.metric('increment', 'journals') return journalid
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 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 verify(token, ip_address=None): lo = d.meta.tables["login"] lc = d.meta.tables["logincreate"] query = d.engine.execute(lc.select().where(lc.c.token == token)).first() # Did the token match a pending login create record? if not query: raise WeasylError("logincreateRecordMissing") elif query.invalid: # If the record is explicitly marked as invalid, treat the record as if it doesn't exist. raise WeasylError("logincreateRecordMissing") db = d.connect() with db.begin(): # Create login record userid = db.scalar( lo.insert().returning(lo.c.userid), { "login_name": d.get_sysname(query.username), "last_login": arrow.now(), "email": query.email, "ip_address_at_signup": ip_address, }) # Create profile records db.execute(d.meta.tables["authbcrypt"].insert(), { "userid": userid, "hashsum": query.hashpass, }) db.execute( d.meta.tables["profile"].insert(), { "userid": userid, "username": query.username, "full_name": query.username, "unixtime": arrow.now(), "config": "kscftj", }) db.execute(d.meta.tables["userinfo"].insert(), { "userid": userid, "birthday": query.birthday, }) db.execute(d.meta.tables["userstats"].insert(), { "userid": userid, }) db.execute(d.meta.tables["welcomecount"].insert(), { "userid": userid, }) # Update logincreate records db.execute(lc.delete().where(lc.c.token == token)) d.metric('increment', 'verifiedusers')
def verify(token, ip_address=None): lo = d.meta.tables["login"] lc = d.meta.tables["logincreate"] query = d.engine.execute(lc.select().where(lc.c.token == token)).first() # Did the token match a pending login create record? if not query: raise WeasylError("logincreateRecordMissing") elif query.invalid: # If the record is explicitly marked as invalid, treat the record as if it doesn't exist. raise WeasylError("logincreateRecordMissing") db = d.connect() with db.begin(): # Create login record userid = db.scalar(lo.insert().returning(lo.c.userid), { "login_name": d.get_sysname(query.username), "last_login": arrow.now(), "email": query.email, "ip_address_at_signup": ip_address, }) # Create profile records db.execute(d.meta.tables["authbcrypt"].insert(), { "userid": userid, "hashsum": query.hashpass, }) db.execute(d.meta.tables["profile"].insert(), { "userid": userid, "username": query.username, "full_name": query.username, "unixtime": arrow.now(), "config": "kscftj", }) db.execute(d.meta.tables["userinfo"].insert(), { "userid": userid, "birthday": query.birthday, }) db.execute(d.meta.tables["userstats"].insert(), { "userid": userid, }) db.execute(d.meta.tables["welcomecount"].insert(), { "userid": userid, }) # Update logincreate records db.execute(lc.delete().where(lc.c.token == token)) d.metric('increment', 'verifiedusers')
def verify(token): lo = d.meta.tables["login"] lc = d.meta.tables["logincreate"] query = d.engine.execute(lc.select().where(lc.c.token == token)).first() if not query: raise WeasylError("logincreateRecordMissing") db = d.connect() with db.begin(): # Create login record userid = db.execute( lo.insert().returning(lo.c.userid), { "login_name": d.get_sysname(query.username), "last_login": arrow.now(), "email": query.email, }).scalar() # Create profile records db.execute(d.meta.tables["authbcrypt"].insert(), { "userid": userid, "hashsum": query.hashpass, }) db.execute( d.meta.tables["profile"].insert(), { "userid": userid, "username": query.username, "full_name": query.username, "unixtime": arrow.now(), "config": "kscftj", }) db.execute(d.meta.tables["userinfo"].insert(), { "userid": userid, "birthday": query.birthday, }) db.execute(d.meta.tables["userstats"].insert(), { "userid": userid, }) db.execute(d.meta.tables["welcomecount"].insert(), { "userid": userid, }) # Update logincreate records db.execute(lc.delete().where(lc.c.token == token)) d.metric('increment', 'verifiedusers')
def verify(token): lo = d.meta.tables["login"] lc = d.meta.tables["logincreate"] query = d.engine.execute(lc.select().where(lc.c.token == token)).first() if not query: raise WeasylError("logincreateRecordMissing") db = d.connect() with db.begin(): # Create login record userid = db.scalar(lo.insert().returning(lo.c.userid), { "login_name": d.get_sysname(query.username), "last_login": arrow.now(), "email": query.email, }) # Create profile records db.execute(d.meta.tables["authbcrypt"].insert(), { "userid": userid, "hashsum": query.hashpass, }) db.execute(d.meta.tables["profile"].insert(), { "userid": userid, "username": query.username, "full_name": query.username, "unixtime": arrow.now(), "config": "kscftj", }) db.execute(d.meta.tables["userinfo"].insert(), { "userid": userid, "birthday": query.birthday, }) db.execute(d.meta.tables["userstats"].insert(), { "userid": userid, }) db.execute(d.meta.tables["welcomecount"].insert(), { "userid": userid, }) # Update logincreate records db.execute(lc.delete().where(lc.c.token == token)) d.metric('increment', 'verifiedusers')
def create(userid, journal, friends_only=False, tags=None): # Check invalid arguments if not journal.title: raise WeasylError("titleInvalid") elif not journal.content: raise WeasylError("contentInvalid") elif not journal.rating: raise WeasylError("ratingInvalid") profile.check_user_rating_allowed(userid, journal.rating) # Assign settings settings = "f" if friends_only else "" # Create journal jo = d.meta.tables["journal"] journalid = d.engine.scalar( jo.insert().returning(jo.c.journalid), { "userid": userid, "title": journal.title, "rating": journal.rating.code, "unixtime": arrow.now(), "settings": settings, }) # Write journal file files.make_path(journalid, "journal") files.write(files.make_resource(userid, journalid, "journal/submit"), journal.content) # Assign search tags searchtag.associate(userid, tags, journalid=journalid) # Create notifications if "m" not in settings: welcome.journal_insert(userid, journalid, rating=journal.rating.code, settings=settings) d.metric('increment', 'journals') return journalid
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 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 append(mailto, mailfrom, subject, content, displayto=None): """Send an e-mail. `mailto` must be a list of e-mail addresses to send this e-mail to. If `mailfrom` is None, the system email will be designated as the sender. Otherwise, `mailfrom` must be a single e-mail address. The 'To' header of the e-mail will be a comma-separated list of the `mailto` addresses unless `displayto` is not None (in which case it will be set to `displayto`.) """ if not mailfrom: mailfrom = macro.MACRO_EMAIL_ADDRESS mailfrom = normalize_address(mailfrom) subject = subject.strip() content = content.strip() if not mailto: raise error.WeasylError("mailtoInvalid") elif not mailfrom: raise error.WeasylError("mailfromInvalid") elif not content: raise error.WeasylError("contentInvalid") if not subject: subject = "None" message = email.mime.text.MIMEText(content.strip()) if displayto is not None: message["To"] = displayto else: message["To"] = ', '.join(mailto) message["From"] = mailfrom message["Subject"] = subject sendmail_args = ['sendmail'] + list(mailto) proc = subprocess.Popen(sendmail_args, stdin=subprocess.PIPE) proc.communicate(message.as_string()) if proc.returncode: raise subprocess.CalledProcessError(proc.returncode, sendmail_args) define.metric('increment', 'emails')
def create(userid, journal, friends_only=False, tags=None): # Check invalid arguments if not journal.title: raise WeasylError("titleInvalid") elif not journal.content: raise WeasylError("contentInvalid") elif not journal.rating: raise WeasylError("ratingInvalid") profile.check_user_rating_allowed(userid, journal.rating) # Assign settings settings = "f" if friends_only else "" # Create journal jo = d.meta.tables["journal"] journalid = d.engine.scalar(jo.insert().returning(jo.c.journalid), { "userid": userid, "title": journal.title, "rating": journal.rating.code, "unixtime": arrow.now(), "settings": settings, }) # Write journal file files.make_path(journalid, "journal") files.write(files.make_resource(userid, journalid, "journal/submit"), journal.content) # Assign search tags searchtag.associate(userid, tags, journalid=journalid) # Create notifications if "m" not in settings: welcome.journal_insert(userid, journalid, rating=journal.rating.code, settings=settings) d.metric('increment', 'journals') return journalid
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 create_literary(userid, submission, embedlink=None, friends_only=False, tags=None, coverfile=None, thumbfile=None, submitfile=None, critique=False, create_notifications=True): premium = d.get_premium(userid) if embedlink: check_google_doc_embed_data(embedlink) # Determine filesizes coversize = len(coverfile) thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize and not embedlink: raise WeasylError("submitSizeZero") elif coversize > 10 * _MEGABYTE: raise WeasylError("coverSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: raise WeasylError("thumbSizeExceedsLimit") if submitsize: submitextension = files.get_extension_for_category(submitfile, m.TEXT_SUBMISSION_CATEGORY) if submitextension is None: raise WeasylError("submitType") if _limit(submitsize, submitextension, premium): raise WeasylError("submitSizeExceedsLimit") submit_media_item = orm.fetch_or_create_media_item( submitfile, file_type=submitextension.lstrip('.')) check_for_duplicate_media(userid, submit_media_item.mediaid) else: submit_media_item = None thumb_media_item = media.make_cover_media_item(thumbfile) cover_media_item = media.make_cover_media_item(coverfile) if cover_media_item and not thumb_media_item: thumb_media_item = cover_media_item # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") if embedlink: settings.append('D') settings = "".join(settings) # Create submission # TODO(kailys): use ORM object db = d.connect() now = arrow.get() try: q = ( d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating.code, "settings": settings, "sorttime": now, }]) .returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) if embedlink: q = (d.meta.tables['google_doc_embeds'].insert() .values(submitid=submitid, embed_url=embedlink)) db.execute(q) except: files.clear_temporary(userid) raise # Assign search tags searchtag.associate(userid, tags, submitid=submitid) if submit_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'submission', submit_media_item) if cover_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'cover', cover_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings, submission.title, tags) # Clear temporary files files.clear_temporary(userid) d.metric('increment', 'submissions') d.metric('increment', 'literarysubmissions') return submitid, bool(thumb_media_item)
def insert(userid, submitid=None, charid=None, journalid=None, parentid=None, content=None): if not submitid and not charid and not journalid: raise WeasylError("Unexpected") elif not content: raise WeasylError("commentInvalid") # Determine indent and parentuserid if parentid: query = d.execute("SELECT userid, indent FROM %s WHERE commentid = %i", ["comments" if submitid else "charcomment" if charid else "journalcomment", parentid], options="single") if not query: raise WeasylError("Unexpected") indent = query[1] + 1 parentuserid = query[0] else: indent = 0 parentuserid = None # Determine otherid otherid = d.execute("SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'", ["submission", "submitid", submitid] if submitid else ["character", "charid", charid] if charid else ["journal", "journalid", journalid], options="element") # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") elif parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar( co.insert() .values(userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow(), indent=indent) .returning(co.c.commentid)) else: commentid = d.execute( "INSERT INTO %s (userid, targetid, parentid, " "content, unixtime, indent) VALUES (%i, %i, %i, '%s', %i, %i) RETURNING " "commentid", [ "charcomment" if charid else "journalcomment", userid, d.get_targetid(submitid, charid, journalid), parentid, content, d.get_time(), indent ], options="element") # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid) d.metric('increment', 'comments') return commentid
def create(userid, character, friends, tags, thumbfile, submitfile): # Make temporary filenames tempthumb = files.get_temporary(userid, "thumb") tempsubmit = files.get_temporary(userid, "char") # Determine filesizes thumbsize = len(thumbfile) submitsize = len(submitfile) # Check invalid title or rating if not character.char_name: raise WeasylError("characterNameInvalid") elif not character.rating: raise WeasylError("ratingInvalid") profile.check_user_rating_allowed(userid, character.rating) # Write temporary thumbnail file if thumbsize: files.write(tempthumb, thumbfile) thumbextension = files.get_extension_for_category( thumbfile, macro.ART_SUBMISSION_CATEGORY) else: thumbextension = None # Write temporary submission file if submitsize: files.write(tempsubmit, submitfile) submitextension = files.get_extension_for_category( submitfile, macro.ART_SUBMISSION_CATEGORY) else: submitextension = None # Check invalid file data if not submitsize: files.clear_temporary(userid) raise WeasylError("submitSizeZero") elif submitsize > _MAIN_IMAGE_SIZE_LIMIT: files.clear_temporary(userid) raise WeasylError("submitSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: files.clear_temporary(userid) raise WeasylError("thumbSizeExceedsLimit") elif submitextension not in [".jpg", ".png", ".gif"]: files.clear_temporary(userid) raise WeasylError("submitType") elif thumbsize and thumbextension not in [".jpg", ".png", ".gif"]: files.clear_temporary(userid) raise WeasylError("thumbType") # Assign settings settings = [] settings.append("f" if friends else "") settings.append(files.typeflag("submit", submitextension)) settings.append(files.typeflag("cover", submitextension)) settings = "".join(settings) # Insert submission data ch = define.meta.tables["character"] try: charid = define.engine.scalar(ch.insert().returning(ch.c.charid), { "userid": userid, "unixtime": arrow.now(), "char_name": character.char_name, "age": character.age, "gender": character.gender, "height": character.height, "weight": character.weight, "species": character.species, "content": character.content, "rating": character.rating.code, "settings": settings, }) except PostgresError: files.clear_temporary(userid) raise # Assign search tags searchtag.associate(userid, tags, charid=charid) # Make submission file files.make_character_directory(charid) files.copy(tempsubmit, files.make_resource(userid, charid, "char/submit", submitextension)) # Make cover file image.make_cover(tempsubmit, files.make_resource(userid, charid, "char/cover", submitextension)) # Make thumbnail selection file if thumbsize: image.make_cover( tempthumb, files.make_resource(userid, charid, "char/.thumb")) thumbnail.create(0, 0, 0, 0, charid=charid, remove=False) # Create notifications welcome.character_insert(userid, charid, rating=character.rating.code, settings=settings) # Clear temporary files files.clear_temporary(userid) define.metric('increment', 'characters') return charid
def insert(userid, submitid=None, charid=None, journalid=None, updateid=None, parentid=None, content=None): if submitid: table = "comments" elif charid: table = "charcomment" elif journalid: table = "journalcomment" elif updateid: table = "siteupdatecomment" else: raise WeasylError("Unexpected") if not content: raise WeasylError("commentInvalid") # Determine parent userid if parentid: parentuserid = d.engine.scalar( "SELECT userid FROM {table} WHERE commentid = %(parent)s".format( table=table), parent=parentid, ) if parentuserid is None: raise WeasylError("Unexpected") else: if updateid: parentid = None # parentid == 0 parentuserid = None if updateid: otherid = d.engine.scalar( "SELECT userid FROM siteupdate WHERE updateid = %(update)s", update=updateid) if not otherid: raise WeasylError("submissionRecordMissing") else: # Determine the owner of the target otherid = d.engine.scalar( "SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'" % (("submission", "submitid", submitid) if submitid else ("character", "charid", charid) if charid else ("journal", "journalid", journalid))) # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") if parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar(co.insert().values( userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow()).returning(co.c.commentid)) elif updateid: commentid = d.engine.scalar( "INSERT INTO siteupdatecomment (userid, targetid, parentid, content)" " VALUES (%(user)s, %(update)s, %(parent)s, %(content)s)" " RETURNING commentid", user=userid, update=updateid, parent=parentid, content=content, ) else: commentid = d.engine.scalar( "INSERT INTO {table} (userid, targetid, parentid, content, unixtime)" " VALUES (%(user)s, %(target)s, %(parent)s, %(content)s, %(now)s)" " RETURNING commentid".format( table="charcomment" if charid else "journalcomment"), user=userid, target=d.get_targetid(charid, journalid), parent=parentid or 0, content=content, now=d.get_time(), ) # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid, updateid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid, updateid) d.metric('increment', 'comments') return commentid
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_visual(userid, submission, friends_only, tags, imageURL, thumbfile, submitfile, critique, create_notifications): premium = d.get_premium(userid) if imageURL: resp = d.http_get(imageURL, timeout=5) submitfile = resp.content # Determine filesizes thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize: files.clear_temporary(userid) raise WeasylError("submitSizeZero") elif thumbsize > 10 * _MEGABYTE: files.clear_temporary(userid) raise WeasylError("thumbSizeExceedsLimit") im = image.from_string(submitfile) submitextension = image.image_extension(im) if _limit(submitsize, submitextension, premium): raise WeasylError("submitSizeExceedsLimit") elif submitextension not in [".jpg", ".png", ".gif"]: raise WeasylError("submitType") submit_file_type = submitextension.lstrip('.') submit_media_item = orm.fetch_or_create_media_item( submitfile, file_type=submit_file_type, im=im) check_for_duplicate_media(userid, submit_media_item.mediaid) cover_media_item = submit_media_item.ensure_cover_image(im) # Thumbnail stuff. # Always create a 'generated' thumbnail from the source image. thumb_generated = images.make_thumbnail(im) if thumb_generated is im: thumb_generated_media_item = submit_media_item else: thumb_generated_media_item = orm.fetch_or_create_media_item( thumb_generated.to_buffer(format=submit_file_type), file_type=submit_file_type, im=thumb_generated) # If requested, also create a 'custom' thumbnail. thumb_media_item = media.make_cover_media_item(thumbfile) if thumb_media_item: thumb_custom = images.make_thumbnail(image.from_string(thumbfile)) thumb_custom_media_item = orm.fetch_or_create_media_item( thumb_custom.to_buffer(format=submit_file_type), file_type=submit_file_type, im=thumb_custom) # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") settings = "".join(settings) # TODO(kailys): maintain ORM object db = d.connect() now = arrow.get() q = ( d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating.code, "settings": settings, "sorttime": now, }]).returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'submission', submit_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'cover', cover_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'thumbnail-generated', thumb_generated_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'thumbnail-custom', thumb_custom_media_item) # Assign search tags searchtag.associate(userid, tags, submitid=submitid) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings, submission.title, tags) d.metric('increment', 'submissions') d.metric('increment', 'visualsubmissions') return submitid
def create_visual(userid, submission, friends_only, tags, imageURL, thumbfile, submitfile, critique, create_notifications): premium = d.get_premium(userid) if imageURL: resp = d.http_get(imageURL, timeout=5) submitfile = resp.content # Determine filesizes thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize: files.clear_temporary(userid) raise WeasylError("submitSizeZero") elif thumbsize > 10 * _MEGABYTE: files.clear_temporary(userid) raise WeasylError("thumbSizeExceedsLimit") im = image.from_string(submitfile) submitextension = image.image_extension(im) if _limit(submitsize, submitextension, premium): raise WeasylError("submitSizeExceedsLimit") elif submitextension not in [".jpg", ".png", ".gif"]: raise WeasylError("submitType") submit_file_type = submitextension.lstrip('.') submit_media_item = orm.fetch_or_create_media_item( submitfile, file_type=submit_file_type, im=im) check_for_duplicate_media(userid, submit_media_item.mediaid) cover_media_item = submit_media_item.ensure_cover_image(im) # Thumbnail stuff. # Always create a 'generated' thumbnail from the source image. thumb_generated = images.make_thumbnail(im) if thumb_generated is im: thumb_generated_media_item = submit_media_item else: thumb_generated_media_item = orm.fetch_or_create_media_item( thumb_generated.to_buffer(format=submit_file_type), file_type=submit_file_type, im=thumb_generated) # If requested, also create a 'custom' thumbnail. thumb_media_item = media.make_cover_media_item(thumbfile) if thumb_media_item: thumb_custom = images.make_thumbnail(image.from_string(thumbfile)) thumb_custom_media_item = orm.fetch_or_create_media_item( thumb_custom.to_buffer(format=submit_file_type), file_type=submit_file_type, im=thumb_custom) # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") settings = "".join(settings) # TODO(kailys): maintain ORM object db = d.connect() now = arrow.get() q = ( d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating.code, "settings": settings, "sorttime": now, }]).returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'submission', submit_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'cover', cover_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'thumbnail-generated', thumb_generated_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) orm.SubmissionMediaLink.make_or_replace_link( submitid, 'thumbnail-custom', thumb_custom_media_item) # Assign search tags searchtag.associate(userid, tags, submitid=submitid) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings, submission.title, tags) d.metric('increment', 'submissions') d.metric('increment', 'visualsubmissions') return submitid
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
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(userid, character, friends, tags, thumbfile, submitfile): # Make temporary filenames tempthumb = files.get_temporary(userid, "thumb") tempsubmit = files.get_temporary(userid, "char") # Determine filesizes thumbsize = len(thumbfile) submitsize = len(submitfile) # Check invalid title or rating if not character.char_name: raise WeasylError("characterNameInvalid") elif not character.rating: raise WeasylError("ratingInvalid") profile.check_user_rating_allowed(userid, character.rating) # Write temporary thumbnail file if thumbsize: files.easyupload(tempthumb, thumbfile, "image") thumbextension = files.get_extension_for_category( thumbfile, macro.ART_SUBMISSION_CATEGORY) else: thumbextension = None # Write temporary submission file if submitsize: files.easyupload(tempsubmit, submitfile, "image") submitextension = files.get_extension_for_category( submitfile, macro.ART_SUBMISSION_CATEGORY) else: submitextension = None # Check invalid file data if not submitsize: files.clear_temporary(userid) raise WeasylError("submitSizeZero") elif submitsize > 10 * _MEGABYTE: files.clear_temporary(userid) raise WeasylError("submitSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: files.clear_temporary(userid) raise WeasylError("thumbSizeExceedsLimit") elif submitextension not in [".jpg", ".png", ".gif"]: files.clear_temporary(userid) raise WeasylError("submitType") elif thumbsize and thumbextension not in [".jpg", ".png", ".gif"]: files.clear_temporary(userid) raise WeasylError("thumbType") # Assign settings settings = [] settings.append("f" if friends else "") settings.append(files.typeflag("submit", submitextension)) settings.append(files.typeflag("cover", submitextension)) settings = "".join(settings) # Insert submission data ch = define.meta.tables["character"] try: charid = define.engine.scalar(ch.insert().returning(ch.c.charid), { "userid": userid, "unixtime": arrow.now(), "char_name": character.char_name, "age": character.age, "gender": character.gender, "height": character.height, "weight": character.weight, "species": character.species, "content": character.content, "rating": character.rating.code, "settings": settings, }) except PostgresError: files.clear_temporary(userid) raise # Assign search tags searchtag.associate(userid, tags, charid=charid) # Make submission file files.make_path(charid, "char") files.copy(tempsubmit, files.make_resource(userid, charid, "char/submit", submitextension)) # Make cover file image.make_cover(tempsubmit, files.make_resource(userid, charid, "char/cover", submitextension)) # Make thumbnail selection file if thumbsize: image.make_cover( tempthumb, files.make_resource(userid, charid, "char/.thumb")) thumbnail.create(userid, 0, 0, 0, 0, charid=charid, remove=False) # Create notifications welcome.character_insert(userid, charid, rating=character.rating.code, settings=settings) # Clear temporary files files.clear_temporary(userid) define.metric('increment', 'characters') return charid
def create_visual(userid, submission, friends_only, tags, imageURL, thumbfile, submitfile, critique, create_notifications): if imageURL: resp = d.http_get(imageURL, timeout=5) submitfile = resp.content # Determine filesizes thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize: raise WeasylError("submitSizeZero") elif thumbsize > 10 * _MEGABYTE: raise WeasylError("thumbSizeExceedsLimit") im = image.from_string(submitfile) submitextension = image.image_extension(im) if submitextension not in [".jpg", ".png", ".gif"]: raise WeasylError("submitType") if _limit(submitsize, submitextension): raise WeasylError("submitSizeExceedsLimit") # Check if the submission is spam is_spam = _check_for_spam(submission=submission, userid=userid) submit_file_type = submitextension.lstrip('.') submit_media_item = orm.MediaItem.fetch_or_create( submitfile, file_type=submit_file_type, im=im) check_for_duplicate_media(userid, submit_media_item.mediaid) cover_media_item = submit_media_item.ensure_cover_image(im) # Thumbnail stuff. # Always create a 'generated' thumbnail from the source image. with BytesIO(submitfile) as buf: thumbnail_formats = images_new.get_thumbnail(buf) thumb_generated, thumb_generated_file_type, thumb_generated_attributes = thumbnail_formats.compatible thumb_generated_media_item = orm.MediaItem.fetch_or_create( thumb_generated, file_type=thumb_generated_file_type, attributes=thumb_generated_attributes, ) if thumbnail_formats.webp is None: thumb_generated_media_item_webp = None else: thumb_generated, thumb_generated_file_type, thumb_generated_attributes = thumbnail_formats.webp thumb_generated_media_item_webp = orm.MediaItem.fetch_or_create( thumb_generated, file_type=thumb_generated_file_type, attributes=thumb_generated_attributes, ) # If requested, also create a 'custom' thumbnail. thumb_media_item = media.make_cover_media_item(thumbfile) if thumb_media_item: thumb_custom = images.make_thumbnail(image.from_string(thumbfile)) thumb_custom_media_item = orm.MediaItem.fetch_or_create( thumb_custom.to_buffer(format=submit_file_type), file_type=submit_file_type, im=thumb_custom) # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") settings = "".join(settings) # TODO(kailys): maintain ORM object db = d.connect() now = arrow.get() q = (d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating.code, "settings": settings, "favorites": 0, "is_spam": is_spam, "submitter_ip_address": submission.submitter_ip_address, "submitter_user_agent_id": submission.submitter_user_agent_id, }]).returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) orm.SubmissionMediaLink.make_or_replace_link(submitid, 'submission', submit_media_item) orm.SubmissionMediaLink.make_or_replace_link(submitid, 'cover', cover_media_item) orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-generated', thumb_generated_media_item) if thumb_generated_media_item_webp is not None: orm.SubmissionMediaLink.make_or_replace_link( submitid, 'thumbnail-generated-webp', thumb_generated_media_item_webp) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-custom', thumb_custom_media_item) # Assign search tags searchtag.associate(userid, tags, submitid=submitid) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings) d.metric('increment', 'submissions') d.metric('increment', 'visualsubmissions') return submitid
def create_multimedia(userid, submission, embedlink=None, friends_only=None, tags=None, coverfile=None, thumbfile=None, submitfile=None, critique=False, create_notifications=True, auto_thumb=False): premium = d.get_premium(userid) embedlink = embedlink.strip() # Determine filesizes coversize = len(coverfile) thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize and not embedlink: raise WeasylError("submitSizeZero") elif embedlink and not embed.check_valid(embedlink): raise WeasylError("embedlinkInvalid") elif coversize > 10 * _MEGABYTE: raise WeasylError("coverSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: raise WeasylError("thumbSizeExceedsLimit") if submitsize: submitextension = files.get_extension_for_category(submitfile, m.MULTIMEDIA_SUBMISSION_CATEGORY) if submitextension is None: raise WeasylError("submitType") elif submitextension not in [".mp3", ".swf"] and not embedlink: raise WeasylError("submitType") elif _limit(submitsize, submitextension, premium): raise WeasylError("submitSizeExceedsLimit") submit_media_item = orm.fetch_or_create_media_item( submitfile, file_type=submitextension.lstrip('.')) check_for_duplicate_media(userid, submit_media_item.mediaid) else: submit_media_item = None thumb_media_item = media.make_cover_media_item(thumbfile) cover_media_item = media.make_cover_media_item(coverfile) if cover_media_item and not thumb_media_item: thumb_media_item = cover_media_item tempthumb_media_item = None im = None if auto_thumb: if thumbsize == 0 and coversize == 0: # Fetch default thumbnail from source if available thumb_url = embed.thumbnail(embedlink) if thumb_url: resp = d.http_get(thumb_url, timeout=5) im = image.from_string(resp.content) if not im and (thumbsize or coversize): im = image.from_string(thumbfile or coverfile) if im: tempthumb = images.make_thumbnail(im) tempthumb_type = images.image_file_type(tempthumb) tempthumb_media_item = orm.fetch_or_create_media_item( tempthumb.to_buffer(format=tempthumb_type), file_type=tempthumb_type, im=tempthumb) # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") settings.append("v" if embedlink else "") settings = "".join(settings) # Inject embedlink if embedlink: submission.content = "".join([embedlink, "\n", submission.content]) # Create submission db = d.connect() now = arrow.get() try: q = ( d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating, "settings": settings, "sorttime": now, }]) .returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) except PostgresError: files.clear_temporary(userid) raise # Assign search tags searchtag.associate(userid, tags, submitid=submitid) if submit_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'submission', submit_media_item) if cover_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'cover', cover_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) if tempthumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-custom', tempthumb_media_item) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings, submission.title, tags) # Clear temporary files files.clear_temporary(userid) d.metric('increment', 'submissions') d.metric('increment', 'multimediasubmissions') return submitid, bool(thumb_media_item)
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 create_literary(userid, submission, embedlink=None, friends_only=False, tags=None, coverfile=None, thumbfile=None, submitfile=None, critique=False, create_notifications=True): if embedlink: check_google_doc_embed_data(embedlink) # Determine filesizes coversize = len(coverfile) thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize and not embedlink: raise WeasylError("submitSizeZero") elif coversize > 10 * _MEGABYTE: raise WeasylError("coverSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: raise WeasylError("thumbSizeExceedsLimit") if submitsize: submitextension = files.get_extension_for_category( submitfile, m.TEXT_SUBMISSION_CATEGORY) if submitextension is None: raise WeasylError("submitType") if _limit(submitsize, submitextension): raise WeasylError("submitSizeExceedsLimit") submit_media_item = orm.MediaItem.fetch_or_create( submitfile, file_type=submitextension.lstrip('.')) check_for_duplicate_media(userid, submit_media_item.mediaid) else: submit_media_item = None # Check if the submission is spam is_spam = _check_for_spam(submission=submission, userid=userid) thumb_media_item = media.make_cover_media_item(thumbfile) cover_media_item = media.make_cover_media_item(coverfile) if cover_media_item and not thumb_media_item: thumb_media_item = cover_media_item # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") if embedlink: settings.append('D') settings = "".join(settings) # Create submission # TODO(kailys): use ORM object db = d.connect() now = arrow.get() q = (d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating.code, "settings": settings, "favorites": 0, "is_spam": is_spam, "submitter_ip_address": submission.submitter_ip_address, "submitter_user_agent_id": submission.submitter_user_agent_id, }]).returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) if embedlink: q = (d.meta.tables['google_doc_embeds'].insert().values( submitid=submitid, embed_url=embedlink)) db.execute(q) # Assign search tags searchtag.associate(userid, tags, submitid=submitid) if submit_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'submission', submit_media_item) if cover_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'cover', cover_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings) d.metric('increment', 'submissions') d.metric('increment', 'literarysubmissions') return submitid, bool(thumb_media_item)
def insert(userid, submitid=None, charid=None, journalid=None, parentid=None, content=None): if not submitid and not charid and not journalid: raise WeasylError("Unexpected") elif not content: raise WeasylError("commentInvalid") # Determine indent and parentuserid if parentid: query = d.execute( "SELECT userid, indent FROM %s WHERE commentid = %i", [ "comments" if submitid else "charcomment" if charid else "journalcomment", parentid ], options="single") if not query: raise WeasylError("Unexpected") indent = query[1] + 1 parentuserid = query[0] else: indent = 0 parentuserid = None # Determine otherid otherid = d.execute( "SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'", ["submission", "submitid", submitid] if submitid else ["character", "charid", charid] if charid else ["journal", "journalid", journalid], options="element") # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") elif parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar(co.insert().values(userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow(), indent=indent).returning( co.c.commentid)) else: commentid = d.execute( "INSERT INTO %s (userid, targetid, parentid, " "content, unixtime, indent) VALUES (%i, %i, %i, '%s', %i, %i) RETURNING " "commentid", [ "charcomment" if charid else "journalcomment", userid, d.get_targetid(submitid, charid, journalid), parentid, content, d.get_time(), indent ], options="element") # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid) d.metric('increment', 'comments') return commentid
def create_multimedia(userid, submission, embedlink=None, friends_only=None, tags=None, coverfile=None, thumbfile=None, submitfile=None, critique=False, create_notifications=True, auto_thumb=False): embedlink = embedlink.strip() # Determine filesizes coversize = len(coverfile) thumbsize = len(thumbfile) submitsize = len(submitfile) if not submitsize and not embedlink: raise WeasylError("submitSizeZero") elif embedlink and not embed.check_valid(embedlink): raise WeasylError("embedlinkInvalid") elif coversize > 10 * _MEGABYTE: raise WeasylError("coverSizeExceedsLimit") elif thumbsize > 10 * _MEGABYTE: raise WeasylError("thumbSizeExceedsLimit") if submitsize: submitextension = files.get_extension_for_category( submitfile, m.MULTIMEDIA_SUBMISSION_CATEGORY) if submitextension is None: raise WeasylError("submitType") elif submitextension not in [".mp3", ".swf"] and not embedlink: raise WeasylError("submitType") elif _limit(submitsize, submitextension): raise WeasylError("submitSizeExceedsLimit") submit_media_item = orm.MediaItem.fetch_or_create( submitfile, file_type=submitextension.lstrip('.')) check_for_duplicate_media(userid, submit_media_item.mediaid) else: submit_media_item = None # Check if the submission is spam is_spam = _check_for_spam(submission=submission, userid=userid) thumb_media_item = media.make_cover_media_item(thumbfile) cover_media_item = media.make_cover_media_item(coverfile) if cover_media_item and not thumb_media_item: thumb_media_item = cover_media_item tempthumb_media_item = None im = None if auto_thumb: if thumbsize == 0 and coversize == 0: # Fetch default thumbnail from source if available thumb_url = embed.thumbnail(embedlink) if thumb_url: resp = d.http_get(thumb_url, timeout=5) im = image.from_string(resp.content) if not im and (thumbsize or coversize): im = image.from_string(thumbfile or coverfile) if im: tempthumb = images.make_thumbnail(im) tempthumb_type = images.image_file_type(tempthumb) tempthumb_media_item = orm.MediaItem.fetch_or_create( tempthumb.to_buffer(format=tempthumb_type), file_type=tempthumb_type, im=tempthumb) # Assign settings settings = [] settings.append("f" if friends_only else "") settings.append("q" if critique else "") settings.append("v" if embedlink else "") settings = "".join(settings) # Inject embedlink if embedlink: submission.content = "".join([embedlink, "\n", submission.content]) # Create submission db = d.connect() now = arrow.get() q = (d.meta.tables['submission'].insert().values([{ "folderid": submission.folderid, "userid": userid, "unixtime": now, "title": submission.title, "content": submission.content, "subtype": submission.subtype, "rating": submission.rating, "settings": settings, "favorites": 0, "is_spam": is_spam, "submitter_ip_address": submission.submitter_ip_address, "submitter_user_agent_id": submission.submitter_user_agent_id, }]).returning(d.meta.tables['submission'].c.submitid)) submitid = db.scalar(q) # Assign search tags searchtag.associate(userid, tags, submitid=submitid) if submit_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'submission', submit_media_item) if cover_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'cover', cover_media_item) if thumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-source', thumb_media_item) if tempthumb_media_item: orm.SubmissionMediaLink.make_or_replace_link(submitid, 'thumbnail-custom', tempthumb_media_item) # Create notifications if create_notifications: _create_notifications(userid, submitid, submission.rating, settings) d.metric('increment', 'submissions') d.metric('increment', 'multimediasubmissions') return submitid, bool(thumb_media_item)