Exemple #1
0
def test_stale_records_get_deleted_when_function_is_called():
    token_store = []
    for i in range(20):
        user_name = "testPrepare%d" % (i, )
        email_addr = "*****@*****.**" % (i, )
        user_id = db_utils.create_user(email_addr=email_addr,
                                       username=user_name)
        form_for_request = Bag(email=email_addr,
                               username=user_name,
                               day=arrow.now().day,
                               month=arrow.now().month,
                               year=arrow.now().year)
        resetpassword.request(form_for_request)
        pw_reset_token = d.engine.scalar(
            "SELECT token FROM forgotpassword WHERE userid = %(id)s",
            id=user_id)
        token_store.append(pw_reset_token)
    # All tokens should exist at this point
    for i in range(20):
        assert resetpassword.checktoken(token_store[i])
    # Set 5 tokens to be two hours old (0,5) (7200)
    for i in range(0, 5):
        d.engine.execute(
            "UPDATE forgotpassword SET set_time = %(time)s WHERE token = %(token)s",
            time=d.get_time() - 7200,
            token=token_store[i])
    # Set 5 tokens to be 30 minutes old (5,10) (1800)
    for i in range(5, 10):
        d.engine.execute(
            "UPDATE forgotpassword SET set_time = %(time)s WHERE token = %(token)s",
            time=d.get_time() - 1800,
            token=token_store[i])
    # Set 5 tokens to be 10 minutes old for the last visit time (10,15) (600)
    for i in range(10, 15):
        d.engine.execute(
            "UPDATE forgotpassword SET link_time = %(time)s WHERE token = %(token)s",
            time=d.get_time() - 600,
            token=token_store[i])
    # Set 5 tokens to be 2 minutes old for the last visit time (10,15) (120)
    for i in range(15, 20):
        d.engine.execute(
            "UPDATE forgotpassword SET link_time = %(time)s WHERE token = %(token)s",
            time=d.get_time() - 120,
            token=token_store[i])
    # This should clear all tokens >1hr old, and all tokens >5 minutes from last visit (10 total)
    resetpassword.prepare('foo')
    # This range should be cleared (set_time > 3600)
    for i in range(0, 5):
        assert not resetpassword.checktoken(token_store[i])
    # This range should still be present (set_time < 3600)
    for i in range(5, 10):
        assert resetpassword.checktoken(token_store[i])
    # This range should be cleared (link_time > 300)
    for i in range(10, 15):
        assert not resetpassword.checktoken(token_store[i])
    # This range should still be present (link_time < 300)
    for i in range(15, 20):
        assert resetpassword.checktoken(token_store[i])
Exemple #2
0
def select_query(userid, rating, otherid=None, folderid=None,
                 backid=None, nextid=None, subcat=None, exclude=None,
                 options=[], config=None, profile_page_filter=False,
                 index_page_filter=False, featured_filter=False):
    if config is None:
        config = d.get_config(userid)
    statement = [
        "FROM submission su "
        "INNER JOIN profile pr ON su.userid = pr.userid "
        "LEFT JOIN folder f USING (folderid) "
        "WHERE su.settings !~ 'h'"]
    if profile_page_filter:
        statement.append(" AND COALESCE(f.settings !~ 'u', true)")
    if index_page_filter:
        statement.append(" AND COALESCE(f.settings !~ 'm', true)")
    if featured_filter:
        statement.append(" AND COALESCE(f.settings ~ 'f', false)")

    # Logged in users will see their own submissions regardless of rating
    # EXCEPT if they are in SFW mode
    if userid and not d.is_sfw_mode():
        statement.append(" AND (su.rating <= %i OR su.userid = %i)" % (rating, userid))
    else:
        statement.append(" AND su.rating <= %i" % (rating,))

    if otherid:
        statement.append(" AND su.userid = %i" % (otherid,))

    if folderid:
        statement.append(" AND su.folderid = %i" % (folderid,))

    if exclude:
        statement.append(" AND su.submitid != %i" % (exclude,))

    if subcat:
        statement.append(" AND su.subtype >= %i AND su.subtype < %i" % (subcat, subcat + 1000))

    if "critique" in options:
        statement.append(" AND su.settings ~ 'q' AND su.unixtime > %i" % (d.get_time() - 259200,))

    if backid:
        statement.append(" AND su.submitid > %i" % (backid,))
    elif nextid:
        statement.append(" AND su.submitid < %i" % (nextid,))
    elif "offset" in options:
        statement.append(" AND su.unixtime < %i" % (d.get_time() - 1800,))

    if userid:
        statement.append(m.MACRO_FRIENDUSER_SUBMIT % (userid, userid, userid))

        if not otherid:
            statement.append(m.MACRO_IGNOREUSER % (userid, "su"))

        statement.append(m.MACRO_BLOCKTAG_SUBMIT % (userid, userid))
    else:
        statement.append(" AND su.settings !~ 'f'")
    return statement
Exemple #3
0
def select_query(userid, rating, otherid=None, folderid=None,
                 backid=None, nextid=None, subcat=None, exclude=None,
                 options=[], config=None, profile_page_filter=False,
                 index_page_filter=False, featured_filter=False):
    if config is None:
        config = d.get_config(userid)
    statement = [
        "FROM submission su "
        "INNER JOIN profile pr ON su.userid = pr.userid "
        "LEFT JOIN folder f USING (folderid) "
        "WHERE su.settings !~ 'h'"]
    if profile_page_filter:
        statement.append(" AND COALESCE(f.settings !~ 'u', true)")
    if index_page_filter:
        statement.append(" AND COALESCE(f.settings !~ 'm', true)")
    if featured_filter:
        statement.append(" AND COALESCE(f.settings ~ 'f', false)")

    # Logged in users will see their own submissions regardless of rating
    # EXCEPT if they are in SFW mode
    if userid and not d.is_sfw_mode():
        statement.append(" AND (su.rating <= %i OR su.userid = %i)" % (rating, userid))
    else:
        statement.append(" AND su.rating <= %i" % (rating,))

    if otherid:
        statement.append(" AND su.userid = %i" % (otherid,))

    if folderid:
        statement.append(" AND su.folderid = %i" % (folderid,))

    if exclude:
        statement.append(" AND su.submitid != %i" % (exclude,))

    if subcat:
        statement.append(" AND su.subtype >= %i AND su.subtype < %i" % (subcat, subcat + 1000))

    if "critique" in options:
        statement.append(" AND su.settings ~ 'q' AND su.unixtime > %i" % (d.get_time() - 259200,))

    if backid:
        statement.append(" AND su.submitid > %i" % (backid,))
    elif nextid:
        statement.append(" AND su.submitid < %i" % (nextid,))
    elif "offset" in options:
        statement.append(" AND su.unixtime < %i" % (d.get_time() - 1800,))

    if userid:
        statement.append(m.MACRO_FRIENDUSER_SUBMIT % (userid, userid, userid))

        if not otherid:
            statement.append(m.MACRO_IGNOREUSER % (userid, "su"))

        statement.append(m.MACRO_BLOCKTAG_SUBMIT % (userid, userid))
    else:
        statement.append(" AND su.settings !~ 'f'")
    return statement
Exemple #4
0
def prepare(token):
    # Remove records from the forgotpassword table which have been active for
    # more than one hour, regardless of whether or not the user has clicked the
    # associated link provided to them in the password reset request email, or
    # which have been visited but have not been removed by the password reset
    # script within five minutes of being visited
    d.execute("DELETE FROM forgotpassword WHERE set_time < %i OR link_time > 0 AND link_time < %i",
              [d.get_time() - 3600, d.get_time() - 300])

    # Set the unixtime record for which the link associated with `token` was
    # visited by the user
    d.execute("UPDATE forgotpassword SET link_time = %i WHERE token = '%s'",
              [d.get_time(), token])
Exemple #5
0
def prepare(token):
    # Remove records from the forgotpassword table which have been active for
    # more than one hour, regardless of whether or not the user has clicked the
    # associated link provided to them in the password reset request email, or
    # which have been visited but have not been removed by the password reset
    # script within five minutes of being visited
    d.execute("DELETE FROM forgotpassword WHERE set_time < %i OR link_time > 0 AND link_time < %i",
              [d.get_time() - 3600, d.get_time() - 300])

    # Set the unixtime record for which the link associated with `token` was
    # visited by the user
    d.execute("UPDATE forgotpassword SET link_time = %i WHERE token = '%s'",
              [d.get_time(), token])
Exemple #6
0
def select_streaming(userid, rating, limit, following=True, order_by=None):
    statement = [
        "SELECT userid, pr.username, pr.stream_url, pr.config, pr.stream_text, start_time "
        "FROM profile pr "
        "JOIN user_streams USING (userid) "
        "WHERE end_time > %i" % (d.get_time(),)
    ]

    if userid:
        statement.append(m.MACRO_IGNOREUSER % (userid, "pr"))

        if following:
            pass  # todo
    if order_by:
        statement.append(" ORDER BY %s LIMIT %i" % (order_by, limit))
    else:
        statement.append(" ORDER BY RANDOM() LIMIT %i" % limit)

    ret = [{
        "userid": i[0],
        "username": i[1],
        "stream_url": i[2],
        "stream_text": i[4],
        "stream_time": i[5],
    } for i in d.execute("".join(statement)) if i[2]]

    media.populate_with_user_media(ret)
    return ret
Exemple #7
0
def verify(db, userid, token):
    # Select purchased terms
    terms = define.execute(
        db, "SELECT terms FROM premiumpurchase WHERE token = '%s'", [token],
        ["element"])

    if not terms:
        raise error.WeasylError("tokenInvalid")

    # Select current terms
    current = define.execute(
        db, "SELECT terms FROM userpremium WHERE userid = %i", [userid],
        ["element"])

    # Update premium status
    if current:
        define.execute(
            db, "UPDATE userpremium SET terms = terms + %i WHERE userid = %i",
            [terms, userid])
    else:
        define.execute(db, "INSERT INTO userpremium VALUES (%i, %i, %i)",
                       [userid, define.get_time(), terms])
        define.execute(
            db,
            "UPDATE profile SET config = config || 'd' WHERE userid = %i AND config !~ 'd'",
            [userid])

    define.execute(db, "DELETE FROM premiumpurchase WHERE token = '%s'",
                   [token])
Exemple #8
0
def request(form):
    token = security.generate_key(100)
    email = emailer.normalize_address(form.email)

    # Determine the user associated with `username`; if the user is not found,
    # raise an exception
    user_id = d.engine.scalar("""
        SELECT userid FROM login WHERE email = %(email)s
    """, email=email)

    # If `user_id` exists, then the supplied email was valid; if not valid, do nothing, raising
    #   no errors for plausible deniability of email existence
    if user_id:
        # Insert a record into the forgotpassword table for the user,
        # or update an existing one
        now = d.get_time()
        address = d.get_address()

        d.engine.execute("""
            INSERT INTO forgotpassword (userid, token, set_time, address)
            VALUES (%(id)s, %(token)s, %(time)s, %(address)s)
            ON CONFLICT (userid) DO UPDATE SET
                token = %(token)s,
                set_time = %(time)s,
                address = %(address)s
        """, id=user_id, token=token, time=now, address=address)

        # Generate and send an email to the user containing a password reset link
        emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
Exemple #9
0
def offer(userid, submitid, otherid):
    query = d.engine.execute(
        "SELECT userid, rating, settings FROM submission WHERE submitid = %(id)s",
        id=submitid,
    ).first()

    if not query or "h" in query[2]:
        raise WeasylError("Unexpected")
    elif userid != query[0]:
        raise WeasylError("Unexpected")

    # Check collection acceptability
    if otherid:
        rating = d.get_rating(otherid)

        if rating < query[1]:
            raise WeasylError("collectionUnacceptable")
        if "f" in query[2]:
            raise WeasylError("collectionUnacceptable")
        if ignoreuser.check(otherid, userid):
            raise WeasylError("IgnoredYou")
        if ignoreuser.check(userid, otherid):
            raise WeasylError("YouIgnored")
        if blocktag.check(otherid, submitid=submitid):
            raise WeasylError("collectionUnacceptable")

    try:
        d.execute(
            "INSERT INTO collection (userid, submitid, unixtime) VALUES (%i, %i, %i)",
            [otherid, submitid, d.get_time()])
    except PostgresError:
        raise WeasylError("collectionExists")

    welcome.collectoffer_insert(userid, otherid, submitid)
def test_commish_search_invalid():
    # searcher
    u1 = db_utils.create_user(username="******")

    # user not open for commissions, but with submissions and commish classes defined
    create_commish_searchable_user("u2", commish_status='c')

    # user open for commission but without any commish classes
    u3 = create_commish_searchable_user("u3")
    classid = commishinfo.select_list(u3)["class"][0]["classid"]
    commishinfo.remove_class(u3, classid)

    # user meets all requirements, but is suspended
    u4 = create_commish_searchable_user("u4")
    db_utils.create_suspenduser(u4, "", d.get_time() + 604800)

    # user meets all requirements, but is banned
    u5 = create_commish_searchable_user("u5")
    db_utils.create_banuser(u5, "")

    # user meets all requirements, but is ignored by searching user
    u6 = create_commish_searchable_user("u6")
    db_utils.create_ignoreuser(u1, u6)

    results = commishinfo.select_commissionable(userid=u1,
                                                limit=10,
                                                offset=0,
                                                q="",
                                                commishclass="",
                                                min_price=None,
                                                max_price=None,
                                                currency='')
    assert not results
Exemple #11
0
def request(form):
    token = security.generate_key(100)
    email = emailer.normalize_address(form.email)
    username = d.get_sysname(form.username)

    # Determine the user associated with `username`; if the user is not found,
    # raise an exception
    user = d.engine.execute(
        "SELECT userid, email FROM login WHERE login_name = %(username)s",
        username=username).first()

    if not user:
        raise WeasylError("loginRecordMissing")

    # Check the user's email address against the provided e-mail address,
    # raising an exception if there is a mismatch
    if email != emailer.normalize_address(user.email):
        raise WeasylError("emailInvalid")

    # Insert a record into the forgotpassword table for the user,
    # or update an existing one
    now = d.get_time()
    address = d.get_address()

    d.engine.execute("""
        INSERT INTO forgotpassword (userid, token, set_time, address)
        VALUES (%(id)s, %(token)s, %(time)s, %(address)s)
        ON CONFLICT (userid) DO UPDATE SET
            token = %(token)s,
            set_time = %(time)s,
            address = %(address)s
    """, id=user.userid, token=token, time=now, address=address)

    # Generate and send an email to the user containing a password reset link
    emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
Exemple #12
0
def request(form):
    token = security.generate_key(100)
    email = emailer.normalize_address(form.email)

    # Determine the user associated with `username`; if the user is not found,
    # raise an exception
    user_id = d.engine.scalar("""
        SELECT userid FROM login WHERE email = %(email)s
    """,
                              email=email)

    # If `user_id` exists, then the supplied email was valid; if not valid, do nothing, raising
    #   no errors for plausible deniability of email existence
    if user_id:
        # Insert a record into the forgotpassword table for the user,
        # or update an existing one
        now = d.get_time()
        address = d.get_address()

        d.engine.execute("""
            INSERT INTO forgotpassword (userid, token, set_time, address)
            VALUES (%(id)s, %(token)s, %(time)s, %(address)s)
            ON CONFLICT (userid) DO UPDATE SET
                token = %(token)s,
                set_time = %(time)s,
                address = %(address)s
        """,
                         id=user_id,
                         token=token,
                         time=now,
                         address=address)

        # Generate and send an email to the user containing a password reset link
        emailer.append([email], None, "Weasyl Password Recovery",
                       d.render("email/reset_password.html", [token]))
Exemple #13
0
def select_streaming(userid, rating, limit, following=True, order_by=None):
    statement = [
        "SELECT userid, pr.username, pr.stream_url, pr.config, pr.stream_text, start_time "
        "FROM profile pr "
        "JOIN user_streams USING (userid) "
        "WHERE end_time > %i" % (d.get_time(), )
    ]

    if userid:
        statement.append(m.MACRO_IGNOREUSER % (userid, "pr"))

        if following:
            pass  # todo
    if order_by:
        statement.append(" ORDER BY %s LIMIT %i" % (order_by, limit))
    else:
        statement.append(" ORDER BY RANDOM() LIMIT %i" % limit)

    ret = [{
        "userid": i[0],
        "username": i[1],
        "stream_url": i[2],
        "stream_text": i[4],
        "stream_time": i[5],
    } for i in d.execute("".join(statement)) if i[2]]

    media.populate_with_user_media(ret)
    return ret
Exemple #14
0
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])

    # set the userid on the session
    sess = d.get_weasyl_session()
    sess.userid = userid
    sess.save = True
def test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s", id=user_id)
    release_date = d.get_time() + 60
    d.engine.execute("INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
                     id=user_id, reason='test', rel=release_date)
    result = login.authenticate_bcrypt(username=user_name, password=raw_password, request=None)
    assert result == (user_id, 'suspended')
Exemple #16
0
def run_periodic_tasks():
    now = arrow.utcnow()
    time_now = get_time()

    db = engine.connect()
    with db.begin():
        locked = db.scalar("SELECT pg_try_advisory_xact_lock(0)")
        if not locked:
            return
        last_run = arrow.get(db.scalar("SELECT last_run FROM cron_runs"))
        if not last_run or now < last_run.replace(seconds=59):
            return

        # Recache the latest submissions
        # Every 2 minutes
        if now.minute % 2 == 0:
            index.recent_submissions.refresh()
            log.msg('refreshed recent submissions')

        # Recache the active user counts
        # Every 5 minutes
        if now.minute % 5 == 0:
            active_users.refresh()
            log.msg('refreshed active user counts')

        # Recalculate recently popular submissions
        # Every 10 minutes
        if now.minute % 10 == 0:
            submission.select_recently_popular.refresh()
            log.msg('refreshed recently popular submissions')

        # Delete all records from views table
        # Every 15 minutes
        if now.minute % 15 == 0:
            db.execute("DELETE FROM views")
            log.msg('cleared views')

        # Daily at 0:00
        if now.hour == 0 and now.minute == 0:
            # Delete password resets older than one day
            db.execute("DELETE FROM forgotpassword WHERE set_time < %(expiry)s", expiry=time_now - 86400)
            log.msg('cleared old forgotten password requests')

            # Delete email reset requests older than two days
            db.execute("""
                DELETE FROM emailverify
                WHERE createtimestamp < (NOW() - INTERVAL '2 days')
            """)
            log.msg('cleared stale email change records')

            # Purge stale logincreate records older than seven days
            db.execute("""
                DELETE FROM logincreate
                WHERE unixtime < %(time)s
            """, time=now - (86400 * 2))
            log.msg('cleared stale account creation records')

        db.execute("UPDATE cron_runs SET last_run = %(now)s", now=now.naive)
Exemple #17
0
def remove_all_submissions(userid, only_before=None):
    if not only_before:
        only_before = d.get_time()

    d.engine.execute(
        "DELETE FROM welcome WHERE userid = %(user)s AND type IN (2010, 2030, 2040, 2050) AND unixtime < %(before)s",
        user=userid, before=only_before)

    d._page_header_info.invalidate(userid)
Exemple #18
0
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])

    # set the userid on the session
    sess = d.web.ctx.weasyl_session
    sess.userid = userid
    sess.save = True
Exemple #19
0
def force_resetbirthday(userid, birthday):
    if not birthday:
        raise WeasylError("birthdayInvalid")
    elif birthday > d.get_time():
        raise WeasylError("birthdayInvalid")

    d.execute("UPDATE userinfo SET birthday = %i WHERE userid = %i", [birthday, userid])
    d.execute("UPDATE login SET settings = REPLACE(settings, 'i', '') WHERE userid = %i", [userid])
    d.get_login_settings.invalidate(userid)
Exemple #20
0
def test_stale_records_get_deleted_when_function_is_called():
    token_store = []
    for i in range(20):
        user_name = "testPrepare%d" % (i,)
        email_addr = "*****@*****.**" % (i,)
        user_id = db_utils.create_user(email_addr=email_addr, username=user_name)
        form_for_request = Bag(email=email_addr, username=user_name, day=arrow.now().day,
                               month=arrow.now().month, year=arrow.now().year)
        resetpassword.request(form_for_request)
        pw_reset_token = d.engine.scalar("SELECT token FROM forgotpassword WHERE userid = %(id)s", id=user_id)
        token_store.append(pw_reset_token)
    # All tokens should exist at this point
    for i in range(20):
        assert resetpassword.checktoken(token_store[i])
    # Set 5 tokens to be two hours old (0,5) (7200)
    for i in range(0, 5):
        d.engine.execute("UPDATE forgotpassword SET set_time = %(time)s WHERE token = %(token)s",
                         time=d.get_time() - 7200, token=token_store[i])
    # Set 5 tokens to be 30 minutes old (5,10) (1800)
    for i in range(5, 10):
        d.engine.execute("UPDATE forgotpassword SET set_time = %(time)s WHERE token = %(token)s",
                         time=d.get_time() - 1800, token=token_store[i])
    # Set 5 tokens to be 10 minutes old for the last visit time (10,15) (600)
    for i in range(10, 15):
        d.engine.execute("UPDATE forgotpassword SET link_time = %(time)s WHERE token = %(token)s",
                         time=d.get_time() - 600, token=token_store[i])
    # Set 5 tokens to be 2 minutes old for the last visit time (10,15) (120)
    for i in range(15, 20):
        d.engine.execute("UPDATE forgotpassword SET link_time = %(time)s WHERE token = %(token)s",
                         time=d.get_time() - 120, token=token_store[i])
    # This should clear all tokens >1hr old, and all tokens >5 minutes from last visit (10 total)
    resetpassword.prepare('foo')
    # This range should be cleared (set_time > 3600)
    for i in range(0, 5):
        assert not resetpassword.checktoken(token_store[i])
    # This range should still be present (set_time < 3600)
    for i in range(5, 10):
        assert resetpassword.checktoken(token_store[i])
    # This range should be cleared (link_time > 300)
    for i in range(10, 15):
        assert not resetpassword.checktoken(token_store[i])
    # This range should still be present (link_time < 300)
    for i in range(15, 20):
        assert resetpassword.checktoken(token_store[i])
Exemple #21
0
def edit_streaming_settings(my_userid,
                            userid,
                            profile,
                            set_stream=None,
                            stream_length=0):

    if set_stream == 'start':
        try:
            stream_length = int(stream_length)
        except:
            raise WeasylError("streamDurationOutOfRange")

        if stream_length < 1 or stream_length > 360:
            raise WeasylError("streamDurationOutOfRange")

    if set_stream == 'start' and not profile.stream_url:
        raise WeasylError("streamLocationNotSet")

    # unless we're specifically still streaming, clear the user_streams record
    if set_stream != 'still':
        d.execute("DELETE FROM user_streams WHERE userid = %i", [userid])

    settings_flag = ''
    stream_status = None
    # if we're starting to stream, update user_streams to reflect that
    if set_stream == 'start':
        now = d.get_time()
        stream_end = now + stream_length * 60  # stream_length is minutes; we need seconds
        d.execute("INSERT INTO user_streams VALUES (%i, %i, %i)",
                  [userid, now, stream_end])
        stream_status = 'n'
    # if we're going to stream later, update profile.settings to reflect that
    elif set_stream == 'later':
        settings_flag = stream_status = 'l'

    # if stream_status is None, any rows in `welcome` will get cleared. but, if
    # the user is still streaming, that shouldn't happen. otherwise, `welcome`
    # will get updated with the current stream state.
    if set_stream != 'still':
        welcome.stream_insert(userid, stream_status)

    d.execute(
        "UPDATE profile "
        "SET (stream_text, stream_url, settings) = ('%s', '%s', REGEXP_REPLACE(settings, '[nli]', '') || '%s') "
        "WHERE userid = %i",
        [profile.stream_text, profile.stream_url, settings_flag, userid])

    if my_userid != userid:
        from weasyl import moderation
        note_body = ('- Stream url: %s\n'
                     '- Stream description: %s\n'
                     '- Stream status: %s' %
                     (profile.stream_url, profile.stream_text,
                      STREAMING_ACTION_MAP[set_stream]))
        moderation.note_about(my_userid, userid, 'Streaming settings updated:',
                              note_body)
Exemple #22
0
def edit_streaming_settings(my_userid,
                            userid,
                            profile,
                            set_stream=None,
                            stream_length=0):

    if set_stream == 'start':
        if stream_length < 1 or stream_length > 360:
            raise WeasylError("streamDurationOutOfRange")

        if not profile.stream_url:
            raise WeasylError("streamLocationNotSet")

    # unless we're specifically still streaming, clear the user_streams record
    if set_stream != 'still':
        d.execute("DELETE FROM user_streams WHERE userid = %i", [userid])

    settings_flag = ''
    stream_status = None
    # if we're starting to stream, update user_streams to reflect that
    if set_stream == 'start':
        now = d.get_time()
        stream_end = now + stream_length * 60  # stream_length is minutes; we need seconds
        d.execute("INSERT INTO user_streams VALUES (%i, %i, %i)",
                  [userid, now, stream_end])
        stream_status = 'n'
    # if we're going to stream later, update profile.settings to reflect that
    elif set_stream == 'later':
        settings_flag = stream_status = 'l'

    # if stream_status is None, any rows in `welcome` will get cleared. but, if
    # the user is still streaming, that shouldn't happen. otherwise, `welcome`
    # will get updated with the current stream state.
    if set_stream != 'still':
        welcome.stream_insert(userid, stream_status)

    pr = d.meta.tables['profile']
    d.engine.execute(pr.update().where(pr.c.userid == userid).values({
        'stream_text':
        profile.stream_text,
        'stream_url':
        profile.stream_url,
        'settings':
        sa.func.regexp_replace(pr.c.settings, "[nli]",
                               "").concat(settings_flag),
    }))

    if my_userid != userid:
        from weasyl import moderation
        note_body = ('- Stream url: %s\n'
                     '- Stream description: %s\n'
                     '- Stream status: %s' %
                     (profile.stream_url, profile.stream_text,
                      STREAMING_ACTION_MAP[set_stream]))
        moderation.note_about(my_userid, userid, 'Streaming settings updated:',
                              note_body)
Exemple #23
0
def remove_all_submissions(userid, only_before=None):
    if not only_before:
        only_before = d.get_time()

    d.engine.execute(
        "DELETE FROM welcome WHERE userid = %(user)s AND type IN (2010, 2030, 2040, 2050) AND unixtime < %(before)s",
        user=userid,
        before=only_before)

    d._page_header_info.invalidate(userid)
def test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    release_date = d.get_time() + 60
    db_utils.create_suspenduser(userid=user_id,
                                reason="Testing",
                                release=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, 'suspended')
Exemple #25
0
def run_periodic_tasks():
    # An arrow object representing the current UTC time
    now = arrow.utcnow()

    # An integer representing the current unixtime (with an offset applied sourced from libweasyl.legacy)
    time_now = get_time()

    db = engine.connect()
    with db.begin():
        locked = db.scalar("SELECT pg_try_advisory_xact_lock(0)")
        if not locked:
            return
        last_run = arrow.get(db.scalar("SELECT last_run FROM cron_runs"))
        if not last_run or now < last_run.replace(second=59):
            return

        # Recache the latest submissions
        # Every 2 minutes
        if now.minute % 2 == 0:
            index.recent_submissions.refresh()
            log.msg('refreshed recent submissions')

        # Recalculate recently popular submissions
        # Every 10 minutes
        if now.minute % 10 == 0:
            submission.select_recently_popular.refresh()
            log.msg('refreshed recently popular submissions')

        # Delete all records from views table
        # Every 15 minutes
        if now.minute % 15 == 0:
            db.execute("DELETE FROM views")
            log.msg('cleared views')

        # Daily at 0:00
        if now.hour == 0 and now.minute == 0:
            # Delete password resets older than one day
            db.execute("DELETE FROM forgotpassword WHERE set_time < %(expiry)s", expiry=time_now - 86400)
            log.msg('cleared old forgotten password requests')

            # Delete email reset requests older than two days
            db.execute("""
                DELETE FROM emailverify
                WHERE createtimestamp < (NOW() - INTERVAL '2 days')
            """)
            log.msg('cleared stale email change records')

            # Purge stale logincreate records older than two days
            db.execute("""
                DELETE FROM logincreate
                WHERE created_at < (NOW() - INTERVAL '2 days')
            """)
            log.msg('cleared stale account creation records')

        db.execute("UPDATE cron_runs SET last_run = %(now)s", now=now.naive)
Exemple #26
0
def insert(userid, submitid=None, charid=None, journalid=None):
    if submitid:
        content_table, id_field, target = "submission", "submitid", submitid
    elif charid:
        content_table, id_field, target = "character", "charid", charid
    else:
        content_table, id_field, target = "journal", "journalid", journalid

    query = d.execute("SELECT userid, settings FROM %s WHERE %s = %i",
                      [content_table, id_field, target],
                      options="single")

    if not query:
        raise WeasylError("TargetRecordMissing")
    elif userid == query[0]:
        raise WeasylError("CannotSelfFavorite")
    elif "f" in query[1] and not frienduser.check(userid, query[0]):
        raise WeasylError("FriendsOnly")
    elif ignoreuser.check(userid, query[0]):
        raise WeasylError("YouIgnored")
    elif ignoreuser.check(query[0], userid):
        raise WeasylError("contentOwnerIgnoredYou")

    insert_result = d.engine.execute(
        'INSERT INTO favorite (userid, targetid, type, unixtime) '
        'VALUES (%(user)s, %(target)s, %(type)s, %(now)s) '
        'ON CONFLICT DO NOTHING',
        user=userid,
        target=d.get_targetid(submitid, charid, journalid),
        type='s' if submitid else 'f' if charid else 'j',
        now=d.get_time())

    if insert_result.rowcount == 0:
        return

    # create a list of users to notify
    notified = set(collection.find_owners(submitid))

    # conditions under which "other" should be notified
    def can_notify(other):
        other_jsonb = d.get_profile_settings(other)
        allow_notify = other_jsonb.allow_collection_notifs
        not_ignored = not ignoreuser.check(other, userid)
        return allow_notify and not_ignored

    notified = set(filter(can_notify, notified))
    # always notify for own content
    notified.add(query[0])

    for other in notified:
        welcome.favorite_insert(userid,
                                submitid=submitid,
                                charid=charid,
                                journalid=journalid,
                                otherid=other)
Exemple #27
0
def test_link_time_field_is_updated_when_valid_token_supplied_to_function():
    user_name = "test"
    email_addr = "*****@*****.**"
    user_id = db_utils.create_user(email_addr=email_addr, username=user_name)
    form_for_request = Bag(email=email_addr, username=user_name, day=arrow.now().day,
                           month=arrow.now().month, year=arrow.now().year)
    resetpassword.request(form_for_request)
    pw_reset_token = d.engine.scalar("SELECT token FROM forgotpassword WHERE userid = %(id)s", id=user_id)
    resetpassword.prepare(pw_reset_token)
    link_time = d.engine.scalar("SELECT link_time FROM forgotpassword WHERE token = %(token)s", token=pw_reset_token)
    assert link_time >= d.get_time()
Exemple #28
0
def test_link_time_field_is_updated_when_valid_token_supplied_to_function():
    user_name = "test"
    email_addr = "*****@*****.**"
    user_id = db_utils.create_user(email_addr=email_addr, username=user_name)
    form_for_request = Bag(email=email_addr, username=user_name, day=arrow.now().day,
                           month=arrow.now().month, year=arrow.now().year)
    resetpassword.request(form_for_request)
    pw_reset_token = d.engine.scalar("SELECT token FROM forgotpassword WHERE userid = %(id)s", id=user_id)
    resetpassword.prepare(pw_reset_token)
    link_time = d.engine.scalar("SELECT link_time FROM forgotpassword WHERE token = %(token)s", token=pw_reset_token)
    assert link_time >= d.get_time()
Exemple #29
0
def test_true_returned_if_token_exists():
    user_id = db_utils.create_user(username='******')
    token = "testtokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentest000001"
    d.engine.execute(d.meta.tables["forgotpassword"].insert(), {
        "userid": user_id,
        "token": token,
        "set_time": d.get_time(),
        "link_time": 0,
        "address": d.get_address(),
    })
    assert resetpassword.prepare(token)
Exemple #30
0
def test_true_returned_if_token_exists():
    user_id = db_utils.create_user(username='******')
    token = "testtokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentesttokentest000001"
    d.engine.execute(d.meta.tables["forgotpassword"].insert(), {
        "userid": user_id,
        "token": token,
        "set_time": d.get_time(),
        "link_time": 0,
        "address": d.get_address(),
    })
    assert resetpassword.checktoken(token)
Exemple #31
0
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
Exemple #32
0
def prepare(token):
    # Remove records from the forgotpassword table which have been active for
    # more than one hour, regardless of whether or not the user has clicked the
    # associated link provided to them in the password reset request email, or
    # which have been visited but have not been removed by the password reset
    # script within five minutes of being visited
    d.engine.execute(
        "DELETE FROM forgotpassword WHERE set_time < %(set_cutoff)s OR link_time > 0 AND link_time < %(link_cutoff)s",
        set_cutoff=d.get_time() - 3600,
        link_cutoff=d.get_time() - 300,
    )

    # Set the unixtime record for which the link associated with `token` was
    # visited by the user
    result = d.engine.execute(
        "UPDATE forgotpassword SET link_time = %(now)s WHERE token = %(token)s",
        now=d.get_time(),
        token=token,
    )

    return result.rowcount == 1
Exemple #33
0
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 test_login_fails_if_user_is_suspended():
    user_id = db_utils.create_user(password=raw_password, username=user_name)
    d.engine.execute("UPDATE login SET settings = 's' WHERE userid = %(id)s",
                     id=user_id)
    release_date = d.get_time() + 60
    d.engine.execute(
        "INSERT INTO suspension VALUES (%(id)s, %(reason)s, %(rel)s)",
        id=user_id,
        reason='test',
        rel=release_date)
    result = login.authenticate_bcrypt(username=user_name,
                                       password=raw_password,
                                       request=None)
    assert result == (user_id, 'suspended')
Exemple #35
0
def edit_streaming_settings(my_userid, userid, profile, set_stream=None, stream_length=0):

    if set_stream == 'start':
        try:
            stream_length = int(stream_length)
        except:
            raise WeasylError("streamDurationOutOfRange")

        if stream_length < 1 or stream_length > 360:
            raise WeasylError("streamDurationOutOfRange")

    if set_stream == 'start' and not profile.stream_url:
        raise WeasylError("streamLocationNotSet")

    # unless we're specifically still streaming, clear the user_streams record
    if set_stream != 'still':
        d.execute("DELETE FROM user_streams WHERE userid = %i", [userid])

    settings_flag = ''
    stream_status = None
    # if we're starting to stream, update user_streams to reflect that
    if set_stream == 'start':
        now = d.get_time()
        stream_end = now + stream_length * 60  # stream_length is minutes; we need seconds
        d.execute("INSERT INTO user_streams VALUES (%i, %i, %i)", [userid, now, stream_end])
        stream_status = 'n'
    # if we're going to stream later, update profile.settings to reflect that
    elif set_stream == 'later':
        settings_flag = stream_status = 'l'

    # if stream_status is None, any rows in `welcome` will get cleared. but, if
    # the user is still streaming, that shouldn't happen. otherwise, `welcome`
    # will get updated with the current stream state.
    if set_stream != 'still':
        welcome.stream_insert(userid, stream_status)

    d.execute(
        "UPDATE profile "
        "SET (stream_text, stream_url, settings) = ('%s', '%s', REGEXP_REPLACE(settings, '[nli]', '') || '%s') "
        "WHERE userid = %i",
        [profile.stream_text, profile.stream_url, settings_flag, userid])

    if my_userid != userid:
        from weasyl import moderation
        note_body = (
            '- Stream url: %s\n'
            '- Stream description: %s\n'
            '- Stream status: %s' % (profile.stream_url, profile.stream_text, STREAMING_ACTION_MAP[set_stream]))
        moderation.note_about(my_userid, userid, 'Streaming settings updated:', note_body)
Exemple #36
0
def insert(userid, submitid=None, charid=None, journalid=None):
    if submitid:
        content_table, id_field, target = "submission", "submitid", submitid
    elif charid:
        content_table, id_field, target = "character", "charid", charid
    else:
        content_table, id_field, target = "journal", "journalid", journalid

    query = d.execute("SELECT userid, settings FROM %s WHERE %s = %i",
                      [content_table, id_field, target], options="single")

    if not query:
        raise WeasylError("TargetRecordMissing")
    elif userid == query[0]:
        raise WeasylError("CannotSelfFavorite")
    elif "f" in query[1] and not frienduser.check(userid, query[0]):
        raise WeasylError("FriendsOnly")
    elif ignoreuser.check(userid, query[0]):
        raise WeasylError("YouIgnored")
    elif ignoreuser.check(query[0], userid):
        raise WeasylError("contentOwnerIgnoredYou")

    insert_result = d.engine.execute(
        'INSERT INTO favorite (userid, targetid, type, unixtime) '
        'VALUES (%(user)s, %(target)s, %(type)s, %(now)s) '
        'ON CONFLICT DO NOTHING',
        user=userid,
        target=d.get_targetid(submitid, charid, journalid),
        type='s' if submitid else 'f' if charid else 'j',
        now=d.get_time())

    if insert_result.rowcount == 0:
        return

    # create a list of users to notify
    notified = set(collection.find_owners(submitid))

    # conditions under which "other" should be notified
    def can_notify(other):
        other_jsonb = d.get_profile_settings(other)
        allow_notify = other_jsonb.allow_collection_notifs
        not_ignored = not ignoreuser.check(other, userid)
        return allow_notify and not_ignored
    notified = set(filter(can_notify, notified))
    # always notify for own content
    notified.add(query[0])

    for other in notified:
        welcome.favorite_insert(userid, submitid=submitid, charid=charid, journalid=journalid, otherid=other)
Exemple #37
0
def reset(form):
    from weasyl import login

    # Raise an exception if `password` does not enter `passcheck` (indicating
    # that the user mistyped one of the fields) or if `password` does not meet
    # the system's password security requirements
    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    # Select the user information and record data from the forgotpassword table
    # pertaining to `token`, requiring that the link associated with the record
    # be visited no more than five minutes prior; if the forgotpassword record is
    # not found or does not meet this requirement, raise an exception
    query = d.engine.execute("""
        SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address
        FROM login lo
            INNER JOIN userinfo ui USING (userid)
            INNER JOIN forgotpassword fp USING (userid)
        WHERE fp.token = %(token)s AND fp.link_time > %(cutoff)s
    """,
                             token=form.token,
                             cutoff=d.get_time() - 300).first()

    if not query:
        raise WeasylError("forgotpasswordRecordMissing")

    USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query

    # Check `username` and `email` against known correct values and raise an
    # exception if there is a mismatch
    if emailer.normalize_address(
            form.email) != emailer.normalize_address(EMAIL):
        raise WeasylError("emailIncorrect")
    elif d.get_sysname(form.username) != USERNAME:
        raise WeasylError("usernameIncorrect")
    elif d.get_address() != ADDRESS:
        raise WeasylError("addressInvalid")

    # Update the authbcrypt table with a new password hash
    d.engine.execute(
        'INSERT INTO authbcrypt (userid, hashsum) VALUES (%(user)s, %(hash)s) '
        'ON CONFLICT (userid) DO UPDATE SET hashsum = %(hash)s',
        user=USERID,
        hash=login.passhash(form.password))

    d.engine.execute("DELETE FROM forgotpassword WHERE token = %(token)s",
                     token=form.token)
Exemple #38
0
def run_periodic_tasks():
    web.ctx.clear()

    now = arrow.utcnow()
    time_now = get_time()

    db = connect().connection()
    with db.begin():
        locked = db.scalar("SELECT pg_try_advisory_xact_lock(0)")
        if not locked:
            return
        last_run = arrow.get(db.scalar("SELECT last_run FROM cron_runs"))
        if not last_run or now < last_run.replace(seconds=59):
            return

        # Recache the latest submissions
        # Every 2 minutes
        if now.minute % 2 == 0:
            index.recent_submissions.refresh()
            log.msg('refreshed recent submissions')

        # Recache the active user counts
        # Every 5 minutes
        if now.minute % 5 == 0:
            active_users.refresh()
            log.msg('refreshed active user counts')

        # Recalculate recently popular submissions
        # Every 10 minutes
        if now.minute % 10 == 0:
            submission.select_recently_popular.refresh()
            log.msg('refreshed recently popular submissions')

        # Delete all records from contentview table
        # Every 15 minutes
        if now.minute % 15 == 0:
            db.execute("DELETE FROM views")
            log.msg('cleared views')

        # Delete password resets older than one day
        # Daily at 0:00
        if now.hour == 0 and now.minute == 0:
            db.execute(
                "DELETE FROM forgotpassword WHERE set_time < %(expiry)s",
                expiry=time_now - 86400)
            log.msg('cleared old forgotten password requests')

        db.execute("UPDATE cron_runs SET last_run = %(now)s", now=now.naive)
Exemple #39
0
def reset(form):
    import login

    # Raise an exception if `password` does not enter `passcheck` (indicating
    # that the user mistyped one of the fields) or if `password` does not meet
    # the system's password security requirements
    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    # Select the user information and record data from the forgotpassword table
    # pertaining to `token`, requiring that the link associated with the record
    # be visited no more than five minutes prior; if the forgotpassword record is
    # not found or does not meet this requirement, raise an exception
    query = d.execute("""
        SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address
        FROM login lo
            INNER JOIN userinfo ui USING (userid)
            INNER JOIN forgotpassword fp USING (userid)
        WHERE fp.token = '%s' AND fp.link_time > %i
    """, [form.token, d.get_time() - 300], options="single")

    if not query:
        raise WeasylError("forgotpasswordRecordMissing")

    USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query

    # Check `username` and `email` against known correct values and raise an
    # exception if there is a mismatch
    if emailer.normalize_address(form.email) != emailer.normalize_address(EMAIL):
        raise WeasylError("emailIncorrect")
    elif d.get_sysname(form.username) != USERNAME:
        raise WeasylError("usernameIncorrect")
    elif d.get_address() != ADDRESS:
        raise WeasylError("addressInvalid")

    # Update the authbcrypt table with a new password hash
    """ TODO TEMPORARY """
    try:
        d.execute("INSERT INTO authbcrypt VALUES (%i, '')", [USERID])
    except:
        pass

    d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(form.password), USERID])

    d.execute("DELETE FROM forgotpassword WHERE token = '%s'", [form.token])
Exemple #40
0
def reset(form):
    import login

    # Raise an exception if `password` does not enter `passcheck` (indicating
    # that the user mistyped one of the fields) or if `password` does not meet
    # the system's password security requirements
    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    # Select the user information and record data from the forgotpassword table
    # pertaining to `token`, requiring that the link associated with the record
    # be visited no more than five minutes prior; if the forgotpassword record is
    # not found or does not meet this requirement, raise an exception
    query = d.execute("""
        SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address
        FROM login lo
            INNER JOIN userinfo ui USING (userid)
            INNER JOIN forgotpassword fp USING (userid)
        WHERE fp.token = '%s' AND fp.link_time > %i
    """, [form.token, d.get_time() - 300], options="single")

    if not query:
        raise WeasylError("forgotpasswordRecordMissing")

    USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query

    # Check `username` and `email` against known correct values and raise an
    # exception if there is a mismatch
    if emailer.normalize_address(form.email) != emailer.normalize_address(EMAIL):
        raise WeasylError("emailIncorrect")
    elif d.get_sysname(form.username) != USERNAME:
        raise WeasylError("usernameIncorrect")
    elif d.get_address() != ADDRESS:
        raise WeasylError("addressInvalid")

    # Update the authbcrypt table with a new password hash
    """ TODO TEMPORARY """
    try:
        d.execute("INSERT INTO authbcrypt VALUES (%i, '')", [USERID])
    except:
        pass

    d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(form.password), USERID])

    d.execute("DELETE FROM forgotpassword WHERE token = '%s'", [form.token])
Exemple #41
0
def request(form):
    token = security.generate_key(100)
    email = emailer.normalize_address(form.email)
    username = d.get_sysname(form.username)

    # Determine the user associated with `username`; if the user is not found,
    # raise an exception
    user = d.engine.execute(
        "SELECT userid, email FROM login WHERE login_name = %(username)s",
        username=username).first()

    if not user:
        raise WeasylError("loginRecordMissing")

    # Check the user's email address against the provided e-mail address,
    # raising an exception if there is a mismatch
    if email != emailer.normalize_address(user.email):
        raise WeasylError("emailInvalid")

    # Insert a record into the forgotpassword table for the user,
    # or update an existing one
    now = d.get_time()
    address = d.get_address()

    try:
        d.engine.execute(
            "INSERT INTO forgotpassword (userid, token, set_time, address)"
            " VALUES (%(id)s, %(token)s, %(time)s, %(address)s)",
            id=user.userid, token=token, time=now, address=address)
    except IntegrityError:
        # An IntegrityError will probably indicate that a password reset request
        # already exists and that the existing row should be updated. If the update
        # doesn't find anything, though, the original error should be re-raised.
        result = d.engine.execute("""
            UPDATE forgotpassword SET
                token = %(token)s,
                set_time = %(time)s,
                address = %(address)s
            WHERE userid = %(id)s
        """, id=user.userid, token=token, time=now, address=address)

        if result.rowcount != 1:
            raise

    # Generate and send an email to the user containing a password reset link
    emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
Exemple #42
0
def pending_accept(userid, submissions):
    if not submissions:
        return

    d.engine.execute(
        "UPDATE collection SET "
        "unixtime = %(now)s, "
        "settings = REGEXP_REPLACE(settings, '[pr]', '') "
        "WHERE settings ~ '[pr]' "
        "AND (submitid, userid) = ANY (%(submissions)s)",
        submissions=submissions, now=d.get_time())

    for s in submissions:
        welcome.collection_insert(s[1], s[0])
        welcome.collectrequest_remove(userid, s[1], s[0])

    d._page_header_info.invalidate(userid)
Exemple #43
0
def select_profile(userid,
                   avatar=False,
                   banner=False,
                   propic=False,
                   images=False,
                   commish=True,
                   viewer=None):
    query = d.execute(
        """
        SELECT pr.username, pr.full_name, pr.catchphrase, pr.unixtime, pr.profile_text,
            pr.settings, pr.stream_url, pr.config, pr.stream_text, lo.settings, us.end_time
        FROM profile pr
            INNER JOIN login lo USING (userid)
            LEFT JOIN user_streams us USING (userid)
        WHERE userid = %i
    """, [userid], ["single"])

    if not query:
        raise WeasylError('RecordMissing')

    streaming_status = "stopped"
    if query[6]:  # profile.stream_url
        if query[10] > d.get_time():  # user_streams.end_time
            streaming_status = "started"
        elif 'l' in query[5]:
            streaming_status = "later"

    return {
        "userid": userid,
        "user_media": media.get_user_media(userid),
        "username": query[0],
        "full_name": query[1],
        "catchphrase": query[2],
        "unixtime": query[3],
        "profile_text": query[4],
        "settings": query[5],
        "stream_url": query[6],
        "stream_text": query[8],
        "config": query[7],
        "show_favorites_bar": "u" not in query[7] and "v" not in query[7],
        "show_favorites_tab": userid == viewer or "v" not in query[7],
        "commish_slots": 0,
        "banned": "b" in query[9],
        "suspended": "s" in query[9],
        "streaming_status": streaming_status,
    }
Exemple #44
0
def run_periodic_tasks():
    web.ctx.clear()

    now = arrow.utcnow()
    time_now = get_time()

    db = connect().connection()
    with db.begin():
        locked = db.scalar("SELECT pg_try_advisory_xact_lock(0)")
        if not locked:
            return
        last_run = arrow.get(db.scalar("SELECT last_run FROM cron_runs"))
        if not last_run or now < last_run.replace(seconds=59):
            return

        # Recache the latest submissions
        # Every 2 minutes
        if now.minute % 2 == 0:
            index.recent_submissions.refresh()
            log.msg('refreshed recent submissions')

        # Recache the active user counts
        # Every 5 minutes
        if now.minute % 5 == 0:
            active_users.refresh()
            log.msg('refreshed active user counts')

        # Recalculate recently popular submissions
        # Every 10 minutes
        if now.minute % 10 == 0:
            submission.select_recently_popular.refresh()
            log.msg('refreshed recently popular submissions')

        # Delete all records from contentview table
        # Every 15 minutes
        if now.minute % 15 == 0:
            db.execute("DELETE FROM views")
            log.msg('cleared views')

        # Delete password resets older than one day
        # Daily at 0:00
        if now.hour == 0 and now.minute == 0:
            db.execute("DELETE FROM forgotpassword WHERE set_time < %(expiry)s", expiry=time_now - 86400)
            log.msg('cleared old forgotten password requests')

        db.execute("UPDATE cron_runs SET last_run = %(now)s", now=now.naive)
Exemple #45
0
def request(form):
    token = security.generate_key(100)
    email = emailer.normalize_address(form.email)
    username = d.get_sysname(form.username)

    # Determine the user associated with `username`; if the user is not found,
    # raise an exception
    user = d.engine.execute(
        "SELECT userid, email FROM login WHERE login_name = %(username)s",
        username=username).first()

    if not user:
        raise WeasylError("loginRecordMissing")

    # Check the user's email address against the provided e-mail address,
    # raising an exception if there is a mismatch
    if email != emailer.normalize_address(user.email):
        raise WeasylError("emailInvalid")

    # Insert a record into the forgotpassword table for the user,
    # or update an existing one
    now = d.get_time()
    address = d.get_address()

    try:
        d.engine.execute(
            "INSERT INTO forgotpassword (userid, token, set_time, address)"
            " VALUES (%(id)s, %(token)s, %(time)s, %(address)s)",
            id=user.userid, token=token, time=now, address=address)
    except IntegrityError:
        # An IntegrityError will probably indicate that a password reset request
        # already exists and that the existing row should be updated. If the update
        # doesn't find anything, though, the original error should be re-raised.
        result = d.engine.execute("""
            UPDATE forgotpassword SET
                token = %(token)s,
                set_time = %(time)s,
                address = %(address)s
            WHERE userid = %(id)s
        """, id=user.userid, token=token, time=now, address=address)

        if result.rowcount != 1:
            raise

    # Generate and send an email to the user containing a password reset link
    emailer.append([email], None, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
Exemple #46
0
def verify(db, userid, token):
    # Select purchased terms
    terms = define.execute(db, "SELECT terms FROM premiumpurchase WHERE token = '%s'", [token], ["element"])

    if not terms:
        raise error.WeasylError("tokenInvalid")

    # Select current terms
    current = define.execute(db, "SELECT terms FROM userpremium WHERE userid = %i", [userid], ["element"])

    # Update premium status
    if current:
        define.execute(db, "UPDATE userpremium SET terms = terms + %i WHERE userid = %i", [terms, userid])
    else:
        define.execute(db, "INSERT INTO userpremium VALUES (%i, %i, %i)", [userid, define.get_time(), terms])
        define.execute(db, "UPDATE profile SET config = config || 'd' WHERE userid = %i AND config !~ 'd'", [userid])

    define.execute(db, "DELETE FROM premiumpurchase WHERE token = '%s'", [token])
Exemple #47
0
def edit(userid, form):
    commentid = d.get_int(form.commentid)

    if form.feature not in ["submit", "char", "journal"]:
        raise WeasylError("Unexpected")

    query = d.execute("SELECT userid, unixtime FROM %scomment WHERE commentid = %i AND settings !~ 'h'",
                      [form.feature, commentid], options="single")

    if not query:
        raise WeasylError("RecordMissing")
    elif userid != query[0] and userid not in staff.MODS:
        raise WeasylError("InsufficientPermissions")
    elif query[1] < d.get_time() - 600:
        raise WeasylError("TimeLimitExceeded")

    d.execute("UPDATE %scomment SET content = '%s' WHERE commentid = %i",
              [form.feature, form.content.strip(), commentid])
Exemple #48
0
def request(userid, submitid, otherid):
    query = d.engine.execute(
        "SELECT userid, rating, settings "
        "FROM submission WHERE submitid = %(submission)s",
        submission=submitid).first()

    rating = d.get_rating(userid)

    if not query or "h" in query.settings:
        raise WeasylError("Unexpected")
    if otherid != query.userid:
        raise WeasylError("Unexpected")

    # not checking for blocktags here because if you want to collect
    # something with a tag you don't like that's your business
    if rating < query.rating:
        raise WeasylError("RatingExceeded")
    if "f" in query.settings:
        raise WeasylError("collectionUnacceptable")
    if ignoreuser.check(otherid, userid):
        raise WeasylError("IgnoredYou")
    if ignoreuser.check(userid, otherid):
        raise WeasylError("YouIgnored")
    if _check_throttle(userid, otherid):
        raise WeasylError("collectionThrottle")

    settings = d.get_profile_settings(otherid)
    if not settings.allow_collection_requests:
        raise WeasylError("Unexpected")

    request_settings = "r"
    try:
        d.engine.execute(
            "INSERT INTO collection (userid, submitid, unixtime, settings) "
            "VALUES (%(userid)s, %(submitid)s, %(now)s, %(settings)s)",
            userid=userid,
            submitid=submitid,
            now=d.get_time(),
            settings=request_settings)
    except PostgresError:
        raise WeasylError("collectionExists")

    welcome.collectrequest_insert(userid, otherid, submitid)
Exemple #49
0
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
Exemple #50
0
def select_profile(userid, viewer=None):
    query = d.engine.execute("""
        SELECT pr.username, pr.full_name, pr.catchphrase, pr.created_at, pr.profile_text,
            pr.settings, pr.stream_url, pr.config, pr.stream_text, us.end_time
        FROM profile pr
            INNER JOIN login lo USING (userid)
            LEFT JOIN user_streams us USING (userid)
        WHERE userid = %(user)s
    """,
                             user=userid).first()

    if not query:
        raise WeasylError('userRecordMissing')

    is_banned, is_suspended = d.get_login_settings(userid)

    streaming_status = "stopped"
    if query[6]:  # profile.stream_url
        if 'l' in query[5]:
            streaming_status = "later"
        elif query[9] is not None and query[9] > d.get_time(
        ):  # user_streams.end_time
            streaming_status = "started"

    return {
        "userid": userid,
        "user_media": media.get_user_media(userid),
        "username": query[0],
        "full_name": query[1],
        "catchphrase": query[2],
        "unixtime": query[3],
        "profile_text": query[4],
        "settings": query[5],
        "stream_url": query[6],
        "stream_text": query[8],
        "config": query[7],
        "show_favorites_bar": "u" not in query[7] and "v" not in query[7],
        "show_favorites_tab": userid == viewer or "v" not in query[7],
        "commish_slots": 0,
        "banned": is_banned,
        "suspended": is_suspended,
        "streaming_status": streaming_status,
    }
Exemple #51
0
def edit(userid, form):
    commentid = d.get_int(form.commentid)

    if form.feature not in ["submit", "char", "journal"]:
        raise WeasylError("Unexpected")

    query = d.execute(
        "SELECT userid, unixtime FROM %scomment WHERE commentid = %i AND settings !~ 'h'",
        [form.feature, commentid],
        options="single")

    if not query:
        raise WeasylError("RecordMissing")
    elif userid != query[0] and userid not in staff.MODS:
        raise WeasylError("InsufficientPermissions")
    elif query[1] < d.get_time() - 600:
        raise WeasylError("TimeLimitExceeded")

    d.execute("UPDATE %scomment SET content = '%s' WHERE commentid = %i",
              [form.feature, form.content.strip(), commentid])
Exemple #52
0
def select_profile(userid, avatar=False, banner=False, propic=False, images=False, commish=True, viewer=None):
    query = d.execute("""
        SELECT pr.username, pr.full_name, pr.catchphrase, pr.unixtime, pr.profile_text,
            pr.settings, pr.stream_url, pr.config, pr.stream_text, lo.settings, us.end_time
        FROM profile pr
            INNER JOIN login lo USING (userid)
            LEFT JOIN user_streams us USING (userid)
        WHERE userid = %i
    """, [userid], ["single"])

    if not query:
        raise WeasylError('RecordMissing')

    streaming_status = "stopped"
    if query[6]:  # profile.stream_url
        if query[10] > d.get_time():  # user_streams.end_time
            streaming_status = "started"
        elif 'l' in query[5]:
            streaming_status = "later"

    return {
        "userid": userid,
        "user_media": media.get_user_media(userid),
        "username": query[0],
        "full_name": query[1],
        "catchphrase": query[2],
        "unixtime": query[3],
        "profile_text": query[4],
        "settings": query[5],
        "stream_url": query[6],
        "stream_text": query[8],
        "config": query[7],
        "show_favorites_bar": "u" not in query[7] and "v" not in query[7],
        "show_favorites_tab": userid == viewer or "v" not in query[7],
        "commish_slots": 0,
        "banned": "b" in query[9],
        "suspended": "s" in query[9],
        "streaming_status": streaming_status,
    }
Exemple #53
0
def send(userid, form):
    form.title = form.title.strip()
    form.content = form.content.strip()

    if not form.content:
        raise WeasylError("contentInvalid")
    elif not form.title:
        raise WeasylError("titleInvalid")
    elif len(form.title) > 100:
        raise WeasylError("titleTooLong")

    users = set(i for i in d.get_userid_list(form.recipient) if i != userid)
    users.difference_update(
        d.execute("SELECT userid FROM ignoreuser WHERE otherid = %i", [userid], options="within"))
    users.difference_update(
        d.execute("SELECT otherid FROM ignoreuser WHERE userid = %i", [userid], options="within"))
    if not users:
        raise WeasylError("recipientInvalid")

    configs = d.execute(
        "SELECT userid, config FROM profile WHERE userid IN %s",
        [d.sql_number_list(list(users))])

    if userid not in staff.MODS:
        # Staff notes only
        users.difference_update(j[0] for j in configs if "y" in j[1])

        # Friend notes only
        users.difference_update(j[0] for j in configs if "z" in j[1] and not frienduser.check(userid, j[0]))

    if not users:
        raise WeasylError("recipientInvalid")
    elif len(users) > 10:
        raise WeasylError("recipientExcessive")

    argv = []
    unixtime = d.get_time()
    statement = ["INSERT INTO message (userid, otherid, title, content, unixtime) VALUES"]

    for i in users:
        argv.extend([form.title if form.title else "None", form.content])
        statement.append(" (%i, %i, '%%s', '%%s', %i)," % (userid, i, unixtime))
        d._page_header_info.invalidate(i)

    statement[-1] = statement[-1][:-1]
    d.execute("".join(statement), argv)

    if form.mod_copy and userid in staff.MODS:
        mod_content = (
            '## The following message was sent as a note to the user.\n\n### %s\n\n%s' % (
                form.title, form.content))
        if form.staff_note:
            mod_content = '%s\n\n%s' % (form.staff_note, mod_content)
        now = arrow.utcnow()
        mod_copies = []
        for target in users:
            mod_copies.append({
                'userid': userid,
                'target_user': target,
                'unixtime': now,
                'settings': 's',
                'content': mod_content,
            })
        d.engine.execute(
            d.meta.tables['comments'].insert()
            .values(mod_copies))
Exemple #54
0
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
Exemple #55
0
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
Exemple #56
0
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
Exemple #57
0
def associate(userid, tags, submitid=None, charid=None, journalid=None, preferred_tags_userid=None, optout_tags_userid=None):
    """
    Associates searchtags with a content item.

    Parameters:
        userid: The userid of the user associating tags
        tags: A set of tags
        submitid: The ID number of a submission content item to associate
        ``tags`` to. (default: None)
        charid: The ID number of a character content item to associate
        ``tags`` to. (default: None)
        journalid: The ID number of a journal content item to associate
        ``tags`` to. (default: None)
        preferred_tags_userid: The ID number of a user to associate
        ``tags`` to for Preferred tags. (default: None)
        optout_tags_userid: The ID number of a user to associate
        ``tags`` to for Opt-Out tags. (default: None)

    Returns:
        A dict containing two elements. 1) ``add_failure_restricted_tags``, which contains a space separated
        string of tag titles which failed to be added to the content item due to the user or global restricted
        tag lists; and 2) ``remove_failure_owner_set_tags``, which contains a space separated string of tag
        titles which failed to be removed from the content item due to the owner of the aforementioned item
        prohibiting users from removing tags set by the content owner.

        If an element does not have tags, the element is set to None. If neither elements are set,
        the function returns None.
    """
    targetid = d.get_targetid(submitid, charid, journalid)

    # Assign table, feature, ownerid
    if submitid:
        table, feature = "searchmapsubmit", "submit"
        ownerid = d.get_ownerid(submitid=targetid)
    elif charid:
        table, feature = "searchmapchar", "char"
        ownerid = d.get_ownerid(charid=targetid)
    elif journalid:
        table, feature = "searchmapjournal", "journal"
        ownerid = d.get_ownerid(journalid=targetid)
    elif preferred_tags_userid:
        table, feature = "artist_preferred_tags", "user"
        targetid = ownerid = preferred_tags_userid
    elif optout_tags_userid:
        table, feature = "artist_optout_tags", "user"
        targetid = ownerid = optout_tags_userid
    else:
        raise WeasylError("Unexpected")

    # Check permissions and invalid target
    if not ownerid:
        raise WeasylError("TargetRecordMissing")
    elif userid != ownerid and ("g" in d.get_config(userid) or preferred_tags_userid or optout_tags_userid):
        # disallow if user is forbidden from tagging, or trying to set artist tags on someone other than themselves
        raise WeasylError("InsufficientPermissions")
    elif ignoreuser.check(ownerid, userid):
        raise WeasylError("contentOwnerIgnoredYou")

    # Determine previous tagids, titles, and settings
    existing = d.engine.execute(
        "SELECT tagid, title, settings FROM {} INNER JOIN searchtag USING (tagid) WHERE targetid = %(target)s".format(table),
        target=targetid).fetchall()

    # Retrieve tag titles and tagid pairs, for new (if any) and existing tags
    query = add_and_get_searchtags(tags)

    existing_tagids = {t.tagid for t in existing}
    entered_tagids = {t.tagid for t in query}

    # Assign added and removed
    added = entered_tagids - existing_tagids
    removed = existing_tagids - entered_tagids

    # enforce the limit on artist preference tags
    if preferred_tags_userid and (len(added) - len(removed) + len(existing)) > MAX_PREFERRED_TAGS:
        raise WeasylError("tooManyPreferenceTags")

    # Track which tags fail to be added or removed to later notify the user (Note: These are tagids at this stage)
    add_failure_restricted_tags = None
    remove_failure_owner_set_tags = None

    # If the modifying user is not the owner of the object, and is not staff, check user/global restriction lists
    if userid != ownerid and userid not in staff.MODS:
        user_rtags = set(query_user_restricted_tags(ownerid))
        global_rtags = set(query_global_restricted_tags())
        add_failure_restricted_tags = remove_restricted_tags(user_rtags | global_rtags, query)
        added -= add_failure_restricted_tags
        if len(add_failure_restricted_tags) == 0:
            add_failure_restricted_tags = None

    # Check removed artist tags
    if not can_remove_tags(userid, ownerid):
        existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings}
        remove_failure_owner_set_tags = removed & existing_artist_tags
        removed.difference_update(existing_artist_tags)
        entered_tagids.update(existing_artist_tags)
        # Submission items use a different method of tag protection for artist tags; ignore them
        if submitid or len(remove_failure_owner_set_tags) == 0:
            remove_failure_owner_set_tags = None

    # Remove tags
    if removed:
        d.engine.execute(
            "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)".format(table),
            target=targetid, removed=list(removed))

    if added:
        d.engine.execute(
            "INSERT INTO {} SELECT tag, %(target)s FROM UNNEST (%(added)s) AS tag".format(table),
            target=targetid, added=list(added))

        # preference/optout tags can only be set by the artist, so this settings column does not apply
        if userid == ownerid and not (preferred_tags_userid or optout_tags_userid):
            d.execute(
                "UPDATE %s SET settings = settings || 'a' WHERE targetid = %i AND tagid IN %s",
                [table, targetid, d.sql_number_list(list(added))])

    if submitid:
        d.engine.execute(
            'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s) '
            'ON CONFLICT (submitid) DO UPDATE SET tags = %(tags)s',
            submission=submitid, tags=list(entered_tagids))

        db = d.connect()
        db.execute(
            d.meta.tables['tag_updates'].insert()
            .values(submitid=submitid, userid=userid,
                    added=tag_array(added), removed=tag_array(removed)))
        if userid != ownerid:
            welcome.tag_update_insert(ownerid, submitid)

    files.append(
        "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()),
        "-%sID %i  -T %i  -UID %i  -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid,
                                               " ".join(tags)))

    # Return dict with any tag titles as a string that failed to be added or removed
    if add_failure_restricted_tags or remove_failure_owner_set_tags:
        if add_failure_restricted_tags:
            add_failure_restricted_tags = " ".join({tag.title for tag in query if tag.tagid in add_failure_restricted_tags})
        if remove_failure_owner_set_tags:
            remove_failure_owner_set_tags = " ".join({tag.title for tag in existing if tag.tagid in remove_failure_owner_set_tags})
        return {"add_failure_restricted_tags": add_failure_restricted_tags,
                "remove_failure_owner_set_tags": remove_failure_owner_set_tags}
    else:
        return None
Exemple #58
0
def associate(userid, tags, submitid=None, charid=None, journalid=None):
    targetid = d.get_targetid(submitid, charid, journalid)

    # Assign table, feature, ownerid
    if submitid:
        table, feature = "searchmapsubmit", "submit"
        ownerid = d.get_ownerid(submitid=targetid)
    elif charid:
        table, feature = "searchmapchar", "char"
        ownerid = d.get_ownerid(charid=targetid)
    else:
        table, feature = "searchmapjournal", "journal"
        ownerid = d.get_ownerid(journalid=targetid)

    # Check permissions and invalid target
    if not ownerid:
        raise WeasylError("TargetRecordMissing")
    elif userid != ownerid and "g" in d.get_config(userid):
        raise WeasylError("InsufficientPermissions")
    elif ignoreuser.check(ownerid, userid):
        raise WeasylError("contentOwnerIgnoredYou")

    # Determine previous tags
    existing = d.engine.execute(
        "SELECT tagid, settings FROM {} WHERE targetid = %(target)s".format(table),
        target=targetid).fetchall()

    # Determine tag titles and tagids
    query = d.engine.execute(
        "SELECT tagid, title FROM searchtag WHERE title = ANY (%(tags)s)",
        tags=list(tags)).fetchall()

    newtags = list(tags - {x.title for x in query})

    if newtags:
        query.extend(
            d.engine.execute(
                "INSERT INTO searchtag (title) SELECT * FROM UNNEST (%(newtags)s) AS title RETURNING tagid, title",
                newtags=newtags
            ).fetchall())

    existing_tagids = {t.tagid for t in existing}
    entered_tagids = {t.tagid for t in query}

    # Assign added and removed
    added = entered_tagids - existing_tagids
    removed = existing_tagids - entered_tagids

    # Check removed artist tags
    if not can_remove_tags(userid, ownerid):
        existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings}
        removed.difference_update(existing_artist_tags)
        entered_tagids.update(existing_artist_tags)

    # Remove tags
    if removed:
        d.engine.execute(
            "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)".format(table),
            target=targetid, removed=list(removed))

    if added:
        d.engine.execute(
            "INSERT INTO {} SELECT tag, %(target)s FROM UNNEST (%(added)s) AS tag".format(table),
            target=targetid, added=list(added))

        if userid == ownerid:
            d.execute(
                "UPDATE %s SET settings = settings || 'a' WHERE targetid = %i AND tagid IN %s",
                [table, targetid, d.sql_number_list(list(added))])

    if submitid:
        d.engine.execute(
            'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s) '
            'ON CONFLICT (submitid) DO UPDATE SET tags = %(tags)s',
            submission=submitid, tags=list(entered_tagids))

        db = d.connect()
        db.execute(
            d.meta.tables['tag_updates'].insert()
            .values(submitid=submitid, userid=userid,
                    added=tag_array(added), removed=tag_array(removed)))
        if userid != ownerid:
            welcome.tag_update_insert(ownerid, submitid)

    files.append(
        "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()),
        "-%sID %i  -T %i  -UID %i  -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid,
                                               " ".join(tags)))