Exemplo n.º 1
0
Arquivo: admin.py Projeto: 0x15/weasyl
    def POST(self):
        form = web.input(ch_username="", ch_full_name="", ch_catchphrase="", ch_email="",
                         ch_birthday="", ch_gender="", ch_country="")
        userid = d.get_int(form.userid)

        if self.user_id != userid and userid in staff.ADMINS and self.user_id not in staff.TECHNICAL:
            return d.errorpage(self.user_id, errorcode.permission)
        if form.get('impersonate'):
            if self.user_id not in staff.TECHNICAL:
                return d.errorpage(self.user_id, errorcode.permission)
            sess = web.ctx.weasyl_session
            sess.additional_data.setdefault('user-stack', []).append(sess.userid)
            sess.additional_data.changed()
            sess.userid = userid
            sess.save = True
            d.append_to_log(
                'staff.actions', userid=self.user_id, action='impersonate', target=userid)
            raise web.seeother('/')
        else:
            profile.do_manage(self.user_id, userid,
                              username=form.username.strip() if form.ch_username else None,
                              full_name=form.full_name.strip() if form.ch_full_name else None,
                              catchphrase=form.catchphrase.strip() if form.ch_catchphrase else None,
                              birthday=form.birthday if form.ch_birthday else None,
                              gender=form.gender if form.ch_gender else None,
                              country=form.country if form.ch_country else None)
            raise web.seeother("/admincontrol")
Exemplo n.º 2
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
Exemplo n.º 3
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
Exemplo n.º 4
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 = 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
Exemplo n.º 5
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
Exemplo n.º 6
0
    def POST(self):
        form = web.input(ch_username="",
                         ch_full_name="",
                         ch_catchphrase="",
                         ch_email="",
                         ch_birthday="",
                         ch_gender="",
                         ch_country="")
        userid = d.get_int(form.userid)

        if self.user_id != userid and userid in staff.ADMINS and self.user_id not in staff.TECHNICAL:
            return d.errorpage(self.user_id, errorcode.permission)
        if form.get('impersonate'):
            if self.user_id not in staff.TECHNICAL:
                return d.errorpage(self.user_id, errorcode.permission)
            sess = web.ctx.weasyl_session
            sess.additional_data.setdefault('user-stack',
                                            []).append(sess.userid)
            sess.additional_data.changed()
            sess.userid = userid
            sess.save = True
            d.append_to_log('staff.actions',
                            userid=self.user_id,
                            action='impersonate',
                            target=userid)
            raise web.seeother('/')
        else:
            profile.do_manage(
                self.user_id,
                userid,
                username=form.username.strip() if form.ch_username else None,
                full_name=form.full_name.strip()
                if form.ch_full_name else None,
                catchphrase=form.catchphrase.strip()
                if form.ch_catchphrase else None,
                birthday=form.birthday if form.ch_birthday else None,
                gender=form.gender if form.ch_gender else None,
                country=form.country if form.ch_country else None)
            raise web.seeother("/admincontrol")
Exemplo n.º 7
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 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
Exemplo n.º 8
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
Exemplo n.º 9
0
def setusermode(userid, form):
    form.userid = profile.resolve(None, form.userid, form.username)
    if not form.userid:
        raise WeasylError('noUser')

    form.reason = form.reason.strip()

    if form.mode == "s":
        if form.datetype == "r":
            # Relative date
            magnitude = int(form.duration)

            if magnitude < 0:
                raise WeasylError("releaseInvalid")

            basedate = datetime.datetime.now()
            if form.durationunit == "y":
                basedate += datetime.timedelta(days=magnitude * 365)
            elif form.durationunit == "m":
                basedate += datetime.timedelta(days=magnitude * 30)
            elif form.durationunit == "w":
                basedate += datetime.timedelta(weeks=magnitude)
            else:  # Catchall, days
                basedate += datetime.timedelta(days=magnitude)

            form.release = d.convert_unixdate(basedate.day, basedate.month, basedate.year)
        else:
            # Absolute date
            if datetime.date(int(form.year), int(form.month), int(form.day)) < datetime.date.today():
                raise WeasylError("releaseInvalid")

            form.release = d.convert_unixdate(form.day, form.month, form.year)
    else:
        form.release = None

    if userid not in staff.MODS:
        raise WeasylError("Unexpected")
    elif form.userid in staff.MODS:
        raise WeasylError("InsufficientPermissions")
    if form.mode == "b":
        query = d.execute(
            "UPDATE login SET settings = REPLACE(REPLACE(settings, 'b', ''), 's', '') || 'b' WHERE userid = %i"
            " RETURNING userid", [form.userid])

        if query:
            d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
            d.execute("DELETE FROM suspension WHERE userid = %i", [form.userid])
            d.execute("INSERT INTO permaban VALUES (%i, '%s')", [form.userid, form.reason])
    elif form.mode == "s":
        if not form.release:
            raise WeasylError("releaseInvalid")

        query = d.execute(
            "UPDATE login SET settings = REPLACE(REPLACE(settings, 'b', ''), 's', '') || 's' WHERE userid = %i"
            " RETURNING userid", [form.userid])

        if query:
            d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
            d.execute("DELETE FROM suspension WHERE userid = %i", [form.userid])
            d.execute("INSERT INTO suspension VALUES (%i, '%s', %i)", [form.userid, form.reason, form.release])
    elif form.mode == "x":
        query = d.execute("UPDATE login SET settings = REPLACE(REPLACE(settings, 's', ''), 'b', '') WHERE userid = %i",
                          [form.userid])
        d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
        d.execute("DELETE FROM suspension WHERE userid = %i", [form.userid])

    action = _mode_to_action_map.get(form.mode)
    if action is not None:
        isoformat_release = None
        message = form.reason
        if form.release is not None:
            isoformat_release = d.datetime.datetime.fromtimestamp(form.release).isoformat()
            message = '#### Release date: %s\n\n%s' % (isoformat_release, message)
        d.append_to_log(
            'staff.actions',
            userid=userid, action=action, target=form.userid, reason=form.reason,
            release=isoformat_release)
        d.get_login_settings.invalidate(form.userid)
        note_about(userid, form.userid, 'User mode changed: action was %r' % (action,), message)
Exemplo n.º 10
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"
    - "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
Exemplo n.º 11
0
def setusermode(userid, form):
    form.userid = profile.resolve(None, form.userid, form.username)
    if not form.userid:
        raise WeasylError('noUser')

    form.reason = form.reason.strip()

    if form.mode == "s":
        if form.datetype == "r":
            # Relative date
            magnitude = int(form.duration)

            if magnitude < 0:
                raise WeasylError("releaseInvalid")

            basedate = datetime.datetime.now()
            if form.durationunit == "y":
                basedate += datetime.timedelta(days=magnitude * 365)
            elif form.durationunit == "m":
                basedate += datetime.timedelta(days=magnitude * 30)
            elif form.durationunit == "w":
                basedate += datetime.timedelta(weeks=magnitude)
            else:  # Catchall, days
                basedate += datetime.timedelta(days=magnitude)

            form.release = d.convert_unixdate(basedate.day, basedate.month, basedate.year)
        else:
            # Absolute date
            if datetime.date(int(form.year), int(form.month), int(form.day)) < datetime.date.today():
                raise WeasylError("releaseInvalid")

            form.release = d.convert_unixdate(form.day, form.month, form.year)
    else:
        form.release = None

    if userid not in staff.MODS:
        raise WeasylError("Unexpected")
    elif form.userid in staff.MODS:
        raise WeasylError("InsufficientPermissions")
    if form.mode == "b":
        # Ban user
        with d.engine.begin() as db:
            db.execute("DELETE FROM permaban WHERE userid = %(target)s", target=form.userid)
            db.execute("DELETE FROM suspension WHERE userid = %(target)s", target=form.userid)
            db.execute("INSERT INTO permaban VALUES (%(target)s, %(reason)s)", target=form.userid, reason=form.reason)
    elif form.mode == "s":
        # Suspend user
        if not form.release:
            raise WeasylError("releaseInvalid")

        with d.engine.begin() as db:
            db.execute("DELETE FROM permaban WHERE userid = %(target)s", target=form.userid)
            db.execute("DELETE FROM suspension WHERE userid = %(target)s", target=form.userid)
            db.execute("INSERT INTO suspension VALUES (%(target)s, %(reason)s, %(release)s)", target=form.userid, reason=form.reason, release=form.release)
    elif form.mode == "x":
        # Unban/Unsuspend
        with d.engine.begin() as db:
            db.execute("DELETE FROM permaban WHERE userid = %(target)s", target=form.userid)
            db.execute("DELETE FROM suspension WHERE userid = %(target)s", target=form.userid)

    action = _mode_to_action_map.get(form.mode)
    if action is not None:
        isoformat_release = None
        message = form.reason
        if form.release is not None:
            isoformat_release = d.datetime.datetime.fromtimestamp(form.release).isoformat()
            message = '#### Release date: %s\n\n%s' % (isoformat_release, message)
        d.append_to_log(
            'staff.actions',
            userid=userid, action=action, target=form.userid, reason=form.reason,
            release=isoformat_release)
        d._get_all_config.invalidate(form.userid)
        note_about(userid, form.userid, 'User mode changed: action was %r' % (action,), message)
Exemplo n.º 12
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
Exemplo n.º 13
0
def setusermode(userid, form):
    form.userid = profile.resolve(None, form.userid, form.username)
    if not form.userid:
        raise WeasylError('noUser')

    form.reason = form.reason.strip()

    if form.mode == "s":
        if form.datetype == "r":
            # Relative date
            magnitude = int(form.duration)

            if magnitude < 0:
                raise WeasylError("releaseInvalid")

            basedate = datetime.datetime.now()
            if form.durationunit == "y":
                basedate += datetime.timedelta(days=magnitude * 365)
            elif form.durationunit == "m":
                basedate += datetime.timedelta(days=magnitude * 30)
            elif form.durationunit == "w":
                basedate += datetime.timedelta(weeks=magnitude)
            else:  # Catchall, days
                basedate += datetime.timedelta(days=magnitude)

            form.release = d.convert_unixdate(basedate.day, basedate.month,
                                              basedate.year)
        else:
            # Absolute date
            if datetime.date(int(form.year), int(form.month), int(
                    form.day)) < datetime.date.today():
                raise WeasylError("releaseInvalid")

            form.release = d.convert_unixdate(form.day, form.month, form.year)
    else:
        form.release = None

    if userid not in staff.MODS:
        raise WeasylError("Unexpected")
    elif form.userid in staff.MODS:
        raise WeasylError("InsufficientPermissions")
    if form.mode == "b":
        query = d.execute(
            "UPDATE login SET settings = REPLACE(REPLACE(settings, 'b', ''), 's', '') || 'b' WHERE userid = %i"
            " RETURNING userid", [form.userid])

        if query:
            d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
            d.execute("DELETE FROM suspension WHERE userid = %i",
                      [form.userid])
            d.execute("INSERT INTO permaban VALUES (%i, '%s')",
                      [form.userid, form.reason])
    elif form.mode == "s":
        if not form.release:
            raise WeasylError("releaseInvalid")

        query = d.execute(
            "UPDATE login SET settings = REPLACE(REPLACE(settings, 'b', ''), 's', '') || 's' WHERE userid = %i"
            " RETURNING userid", [form.userid])

        if query:
            d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
            d.execute("DELETE FROM suspension WHERE userid = %i",
                      [form.userid])
            d.execute("INSERT INTO suspension VALUES (%i, '%s', %i)",
                      [form.userid, form.reason, form.release])
    elif form.mode == "x":
        query = d.execute(
            "UPDATE login SET settings = REPLACE(REPLACE(settings, 's', ''), 'b', '') WHERE userid = %i",
            [form.userid])
        d.execute("DELETE FROM permaban WHERE userid = %i", [form.userid])
        d.execute("DELETE FROM suspension WHERE userid = %i", [form.userid])

    action = _mode_to_action_map.get(form.mode)
    if action is not None:
        isoformat_release = None
        message = form.reason
        if form.release is not None:
            isoformat_release = d.datetime.datetime.fromtimestamp(
                form.release).isoformat()
            message = '#### Release date: %s\n\n%s' % (isoformat_release,
                                                       message)
        d.append_to_log('staff.actions',
                        userid=userid,
                        action=action,
                        target=form.userid,
                        reason=form.reason,
                        release=isoformat_release)
        d.get_login_settings.invalidate(form.userid)
        note_about(userid, form.userid,
                   'User mode changed: action was %r' % (action, ), message)