Beispiel #1
0
class LogMod(ModBase, db.Model):
    """Represents a change made to a mod."""
    __tablename__ = "log_mod"

    # The user that made this change.
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
    user = db.relationship('User', backref='changes')

    # Date this change was made.
    date = db.Column(db.DateTime, default=datetime.datetime.utcnow)

    cur_id = db.Column(db.Integer, db.ForeignKey('mod.id'), nullable=True)
    current = db.relationship("Mod",
                              backref=backref("logs", order_by='LogMod.date'))

    # Index within this mod's list of versions.
    index = db.Column(db.Integer, nullable=False)

    authors = db.relationship("ModAuthor", secondary=authored_by_table)
    mod_vsns = db.relationship("LogModVersion", back_populates="mod")

    def blank(self, **kwargs):
        return LogMod(**kwargs)

    def blank_child(self, **kwargs):
        return LogModVersion(**kwargs)

    def copy_from(self, other):
        if hasattr(other, 'cur_id'): self.cur_id = other.cur_id
        self.cur_id = other.id
        super(ModBase, self).copy_from(other)
Beispiel #2
0
class Mod(ModBase, db.Model):
    __tablename__ = "mod"
    slug = db.Column(db.String(80), nullable=False, unique=True)

    authors = db.relationship(
        "ModAuthor",
        secondary=authored_by_table,
        backref="mods")
    mod_vsns = db.relationship("ModVersion", back_populates="mod")

    def game_versions(self):
        """Returns a list of game versions supported by all the versions of this mod."""
        gvs = GameVersion.query \
            .join(ModVersion, GameVersion.mod_vsns) \
            .filter(ModVersion.mod_id == self.id).all()
        gvsns = set()
        for gv in gvs:
            gvsns.add(gv.name)
        return sorted(list(gvsns))

    def game_versions_str(self):
        """Returns a comma separated string listing the supported game versions for this mod."""
        return ", ".join(map(lambda v: v, self.game_versions()))


    def blank(self, **kwargs): return Mod(**kwargs)
    def blank_child(self, **kwargs): return ModVersion(**kwargs)

    def log_change(self, user):
        entry = LogMod(user=user, cur_id=self.id, index=len(self.logs))
        entry.copy_from(self)
        db.session.add(entry)
        return entry

    @property
    def latest_vsn(self):
        # FIXME: This could probably be done faster with a DB query.
        return self.logs[len(self.logs)-1]

    def make_draft(self, user):
        """Creates a DraftMod based on the latest version of this mod."""
        latest = self.latest_vsn
        draft = DraftMod(user=user)
        draft.copy_from(latest)
        return draft

    def revert_to(self, log):
        """
        Takes a `ModLog` and reverts this mod to its state at the time of that log entry.
        Raises `ValueError` if the given log entry is not for this mod.
        """
        if log.cur_id != self.id:
            raise ValueError('Log entry {} is not for mod {}'.format(log, self))
        self.copy_from(log)
Beispiel #3
0
class ModVersion(ModVersionBase, db.Model):
    __tablename__ = "mod_version"
    mod_id = db.Column(db.Integer, db.ForeignKey('mod.id'))
    mod = db.relationship("Mod", back_populates="mod_vsns")
    game_vsns = db.relationship(
        "GameVersion",
        secondary=for_game_vsn_table,
        backref="mod_vsns")
    files = db.relationship("ModFile", back_populates="version")

    def blank(self, **kwargs): return ModVersion(**kwargs)
    def blank_child(self, **kwargs): return ModFile(**kwargs)
Beispiel #4
0
class LogModFile(ModFileBase, db.Model):
    __tablename__ = "log_mod_file"
    version_id = db.Column(db.Integer, db.ForeignKey('log_mod_version.id'))
    version = db.relationship("LogModVersion", back_populates="files")

    cur_id = db.Column(db.Integer, db.ForeignKey('mod_file.id'), nullable=True)
    current = db.relationship("ModFile")

    def blank(self, **kwargs):
        return LogModFile(**kwargs)

    def copy_from(self, other):
        if hasattr(other, 'cur_id'): self.cur_id = other.cur_id
        self.cur_id = other.id
        super(ModFileBase, self).copy_from(other)
Beispiel #5
0
class ModFile(ModFileBase, db.Model):
    __tablename__ = "mod_file"
    version_id = db.Column(db.Integer, db.ForeignKey('mod_version.id'))
    version = db.relationship("ModVersion", back_populates="files")

    # Whether we're providing our own download links for this file.
    redist = db.Column(db.Boolean)

    def blank(self, **kwargs): return ModFile(**kwargs)
Beispiel #6
0
class LogModVersion(ModVersionBase, db.Model):
    __tablename__ = "log_mod_version"
    mod_id = db.Column(db.Integer, db.ForeignKey('log_mod.id'))
    mod = db.relationship("LogMod", back_populates="mod_vsns")
    game_vsns = db.relationship("GameVersion", secondary=for_game_vsn_table)
    files = db.relationship("LogModFile", back_populates="version")

    cur_id = db.Column(db.Integer,
                       db.ForeignKey('mod_version.id'),
                       nullable=True)
    current = db.relationship("ModVersion")

    def blank(self, **kwargs):
        return LogModVersion(**kwargs)

    def blank_child(self, **kwargs):
        return LogModFile(**kwargs)

    def copy_from(self, other):
        if hasattr(other, 'cur_id'): self.cur_id = other.cur_id
        self.cur_id = other.id
        super(ModVersionBase, self).copy_from(other)
Beispiel #7
0
class UserSetting(db.Model):  # type: ignore
    """
    Table for storing user settings.

    Each setting the user has changed from the defaults is stored as a separate
    row containing a key, value pair in this table.

    This schema allows for new settings to be added without requiring a
    database migration.
    """
    key = db.Column(db.String(40), nullable=False, primary_key=True)
    value = db.Column(db.JSON, nullable=False)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
    user = db.relationship('User')
Beispiel #8
0
class StoredFile(db.Model):
    """Represents a file stored in some sort of storage medium."""
    __tablename__ = 'stored_file'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    sha256 = db.Column(db.String(130), nullable=False)

    upload_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
    upload_by = db.relationship('User')

    # Path to this file within the B2 bucket. Null if file is not on B2.
    b2_path = db.Column(db.String(300), nullable=True)

    def b2_download_url(self):
        """Gets the URL to download this file from the archive's B2 bucket."""
        if self.b2_path:
            return urljoin(app.config['B2_PUBLIC_URL'], self.b2_path)
Beispiel #9
0
class ResetToken(db.Model):
    """Represents a one time use key that allows a user to reset (or set) their password."""
    id = db.Column(db.Integer, primary_key=True)
    token = db.Column(GUID(), nullable=False, unique=True)
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    active = db.Column(db.Boolean, nullable=False, default=True)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User',
                           backref=db.backref('reset_token',
                                              uselist=False,
                                              cascade='all, delete-orphan'))

    def __init__(self, *args, token=None, **kwargs):
        if not token:
            token = uuid.uuid4()
        super(ResetToken, self).__init__(*args, token=token, **kwargs)

    def expired(self):
        return not self.active or \
                self.created < datetime.utcnow() - app.config['PASSWD_RESET_EXPIRE_TIME']
Beispiel #10
0
class Session(db.Model):
    """Represents a user's login session."""
    id = db.Column(db.Integer, primary_key=True)
    # Session ID stored in the user's browser cookies.
    sess_id = db.Column(GUID(), nullable=False, unique=True)

    login_ip = db.Column(db.String(128), nullable=False)
    login_date = db.Column(db.DateTime,
                           nullable=False,
                           default=datetime.utcnow)
    last_seen = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    # User this session is logged in as.
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User',
                           backref=db.backref('sessions',
                                              lazy=True,
                                              order_by=last_seen.desc()))

    active = db.Column(db.Boolean, nullable=False, default=True)

    def __init__(self, *args, sess_id=None, **kwargs):
        if not sess_id:
            sess_id = uuid.uuid4()
        super(Session, self).__init__(*args, sess_id=sess_id, **kwargs)

    def expired(self):
        return not self.active or \
                self.last_seen < datetime.utcnow() - app.config['SERV_SESSION_EXPIRE_TIME']

    def disable(self):
        self.active = False

    def touch(self):
        """Updates this session's last seen date."""
        now = datetime.utcnow()
        self.last_seen = now
        self.user.last_seen = now
Beispiel #11
0
class Mod(ModBase, db.Model):
    __tablename__ = "mod"
    slug = db.Column(db.String(80), nullable=False, unique=True)

    authors = db.relationship(
        "ModAuthor",
        secondary=authored_by_table,
        backref="mods")
    mod_vsns = db.relationship("ModVersion", back_populates="mod")

    # If this is set to false, the mod will be de-listed.
    redist = db.Column(db.Boolean, nullable=False, default=True)

    @staticmethod
    def search_query(game_vsn=None, author=None, keyword=None,
            include_delisted=False):
        """
        Returns a pre-made standard search query for listing mods. Optional
        parameters may be specified for filtering.
        """
        query = Mod.query
        if not include_delisted:
            query = query.filter_by(redist=True)
        if author and len(author) > 0:
            query = query.join(ModAuthor, Mod.authors).filter(ModAuthor.name == author)
        if game_vsn and len(game_vsn) > 0:
            query = query.join(ModVersion) \
                                .join(GameVersion, ModVersion.game_vsns) \
                                .filter(GameVersion.name == game_vsn)
        if keyword and len(keyword) > 0:
            query = query.filter(Mod.name.ilike("%"+keyword+"%"))
        return query

    def game_versions(self):
        """Returns a list of game versions supported by all the versions of this mod."""
        gvs = GameVersion.query \
            .join(ModVersion, GameVersion.mod_vsns) \
            .filter(ModVersion.mod_id == self.id).all()
        gvsns = set()
        for gv in gvs:
            gvsns.add(gv.name)
        return sorted(list(gvsns))

    def game_versions_str(self):
        """Returns a comma separated string listing the supported game versions for this mod."""
        return ", ".join(map(lambda v: v, self.game_versions()))


    def blank(self, **kwargs): return Mod(**kwargs)
    def blank_child(self, **kwargs): return ModVersion(**kwargs)

    def log_change(self, user, approved_by=None):
        """
        Logs a new change for this mod.

        Assigns the change to `user`, and if `approved_by` is not `None`,
        assigns them as the user who approved it.
        """
        entry = LogMod(user=user, approved_by=approved_by, cur_id=self.id, index=len(self.logs))
        entry.copy_from(self)
        db.session.add(entry)
        return entry

    @property
    def latest_vsn(self):
        # FIXME: This could probably be done faster with a DB query.
        return self.logs[len(self.logs)-1]

    def make_draft(self, user):
        """Creates a DraftMod based on the latest version of this mod."""
        latest = self.latest_vsn
        draft = DraftMod(user=user)
        draft.copy_from(latest)
        return draft

    def revert_to(self, log):
        """
        Takes a `ModLog` and reverts this mod to its state at the time of that log entry.
        Raises `ValueError` if the given log entry is not for this mod.
        """
        if log.cur_id != self.id:
            raise ValueError('Log entry {} is not for mod {}'.format(log, self))
        self.copy_from(log)
Beispiel #12
0
class Session(db.Model):
    """Represents a user's login session.

    A session may not be considered fully authenticated if `authed_2fa` is false.
    """
    id = db.Column(db.Integer, primary_key=True)
    # Session ID stored in the user's browser cookies.
    sess_id = db.Column(GUID(), nullable=False, unique=True)

    login_ip = db.Column(db.String(128), nullable=False)
    login_date = db.Column(db.DateTime,
                           nullable=False,
                           default=datetime.utcnow)
    last_seen = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    # User this session is logged in as.
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User',
                           backref=db.backref('sessions',
                                              lazy=True,
                                              order_by=last_seen.desc()))

    authed_2fa = db.Column(db.Boolean, nullable=False, default=False)
    active = db.Column(db.Boolean, nullable=False, default=True)

    def __init__(self, *args, sess_id=None, **kwargs):
        if not sess_id:
            sess_id = uuid.uuid4()

        super(Session, self).__init__(*args, sess_id=sess_id, **kwargs)

    def expired(self):
        if not self.active: return True
        if self.authed_2fa:
            return self.last_seen < datetime.utcnow(
            ) - app.config['SERV_SESSION_EXPIRE_TIME']
        else:
            return self.last_seen < datetime.utcnow() - \
                    app.config['SERV_PARTIAL_SESSION_EXPIRE_TIME']

    def auth_2fa(self, code):
        """Authenticates the user's second factor with the given code.

        Returns true on success, false on failure. If successful, this session
        will be marked as fully authenticated.

        If the user's 2 factor is disabled, this has no effect.
        """
        if self.user.totp_secret and self.user.validate_otp(code):
            self.authed_2fa = True
            db.session.commit()
            return True
        else:
            return False

    def disable(self):
        self.active = False

    def touch(self):
        """Updates this session's last seen date."""
        now = datetime.utcnow()
        self.last_seen = now
        self.user.last_seen = now