Пример #1
0
class Group(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    name = db.Column(db.String(255), unique=True, nullable=False)

    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    owner = db.relationship('User',
                            backref=db.backref('groups',
                                               order_by=id,
                                               lazy='joined'))

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '<Group({name!r})>'.format(name=self.name)

    @classmethod
    def get_or_create(cls, name):
        name = name.lower()

        g = cls.query.filter_by(name=name).first()
        if not g:
            g = Group(name=name)

        return g
Пример #2
0
class AuthToken(db.Model):
    """
    Service authentication tokens, such as those used for Github's OAuth.
    """
    id = db.Column(db.Integer, primary_key=True)
    created = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)
    name = db.Column(db.String(50), nullable=False)
    token = db.Column(db.String(512), nullable=False)

    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    owner = db.relationship('User',
                            backref=db.backref('tokens',
                                               order_by=id,
                                               lazy='dynamic',
                                               cascade='all, delete-orphan'))

    @classmethod
    def new(cls, token, name):
        c = cls()
        c.token = token
        c.name = name
        return c
Пример #3
0
class BotEvent(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    created = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)

    channel = db.Column(db.String(80))
    host = db.Column(db.String(255), nullable=False)
    port = db.Column(db.Integer, default=6667)
    ssl = db.Column(db.Boolean, default=False)

    message = db.Column(db.Text())
    status = db.Column(db.String(30))
    event = db.Column(db.String(255))

    @classmethod
    def new(cls, host, port, ssl, message, status, event, channel=None):
        c = cls()
        c.host = host
        c.port = port
        c.ssl = ssl
        c.message = message
        c.status = status
        c.event = event
        c.channel = channel
        return c
Пример #4
0
class Hook(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    created = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)
    key = db.Column(db.String(255), nullable=False)
    service_id = db.Column(db.Integer)
    config = db.Column(db.PickleType)

    project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
    project = db.relationship('Project',
                              backref=db.backref('hooks',
                                                 order_by=id,
                                                 lazy='dynamic',
                                                 cascade='all, delete-orphan'))

    message_count = db.Column(db.Integer, default=0)

    @classmethod
    def new(cls, service_id, config=None):
        p = cls()
        p.service_id = service_id
        p.key = cls._new_key()
        p.config = config
        return p

    @staticmethod
    def _new_key():
        return base64.urlsafe_b64encode(os.urandom(24))[:24]

    @classmethod
    def by_service_and_project(cls, service_id, project_id):
        return cls.query.filter_by(service_id=service_id,
                                   project_id=project_id).first()

    @property
    def hook(self):
        return HookService.services[self.service_id]

    def absolute_url(self):
        hook = self.hook
        try:
            hook_url = hook.absolute_url(self)
            return hook_url
        except NotImplementedError:
            return None
Пример #5
0
class Project(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    created = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)
    public = db.Column(db.Boolean, default=True)
    website = db.Column(db.String(1024))

    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    owner = db.relationship('User',
                            backref=db.backref('projects',
                                               order_by=id,
                                               lazy='dynamic',
                                               cascade='all, delete-orphan'))

    full_name = db.Column(db.String(101), nullable=False, unique=True)
    message_count = db.Column(db.Integer, default=0)

    @classmethod
    def new(cls, name, public=True, website=None):
        c = cls()
        c.name = name.strip()
        c.public = public
        c.website = website.strip() if website else None
        return c

    @hybrid_property
    def name_i(self):
        return self.name.lower()

    @name_i.comparator
    def name_i(cls):
        return CaseInsensitiveComparator(cls.name)

    @classmethod
    def by_name(cls, name):
        return cls.query.filter_by(name_i=name).first()

    @classmethod
    def by_name_and_owner(cls, name, owner):
        q = cls.query.filter(cls.owner_id == owner.id)
        q = q.filter(cls.name_i == name)
        return q.first()

    @classmethod
    def visible(cls, q, user=None):
        """
        Modifies the sqlalchemy query `q` to only show projects accessible
        to `user`. If `user` is ``None``, only shows public projects.
        """
        if user and user.in_group('admin'):
            # We don't do any filtering for admins,
            # who should have full visibility.
            pass
        elif user:
            # We only show the projects that are either public,
            # or are owned by `user`.
            q = q.filter(
                or_(Project.owner_id == user.id, Project.public == True))
        else:
            q = q.filter(Project.public == True)

        return q

    def is_owner(self, user):
        """
        Returns ``True`` if `user` is the owner of this project.
        """
        return user and user.id == self.owner.id

    def can_see(self, user):
        if self.public:
            # Public projects are always visible.
            return True
        if user and user.in_group('admin'):
            # Admins can always see projects.
            return True
        elif self.is_owner(user):
            # The owner of the project can always see it.
            return True

        return False

    def can_modify(self, user):
        """
        Returns ``True`` if `user` can modify this project.
        """
        if user and user.in_group('admin'):
            # Admins can always modify projects.
            return True
        elif self.is_owner(user):
            return True

        return False
Пример #6
0
class Channel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    created = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)

    channel = db.Column(db.String(80), nullable=False)
    host = db.Column(db.String(255), nullable=False)
    port = db.Column(db.Integer, default=6667)
    ssl = db.Column(db.Boolean, default=False)
    public = db.Column(db.Boolean, default=False)

    project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
    project = db.relationship('Project',
                              backref=db.backref('channels',
                                                 order_by=id,
                                                 lazy='dynamic',
                                                 cascade='all, delete-orphan'))

    @classmethod
    def new(cls, channel, host, port=6667, ssl=False, public=False):
        c = cls()
        c.channel = channel
        c.host = host
        c.port = port
        c.ssl = ssl
        c.public = public
        return c

    @classmethod
    def channel_count_by_network(cls):
        q = (db.session.query(
            Channel.host,
            func.count(Channel.channel).label('count')).filter_by(
                public=True).group_by(Channel.host).order_by('-count'))
        for network, channel_count in q:
            yield network, channel_count

    def last_event(self):
        """
        Returns the latest BotEvent to occur for this channel.
        """
        return BotEvent.query.filter_by(host=self.host,
                                        port=self.port,
                                        ssl=self.ssl,
                                        channel=self.channel).order_by(
                                            BotEvent.created.desc()).first()

    @classmethod
    def visible(cls, q, user=None):
        """
        Modifies the sqlalchemy query `q` to only show channels accessible
        to `user`. If `user` is ``None``, only shows public channels in
        public projects.
        """
        from notifico.models import Project

        if user and user.in_group('admin'):
            # We don't do any filtering for admins,
            # who should have full visibility.
            pass
        else:
            q = q.join(Channel.project).filter(Project.public == True,
                                               Channel.public == True)

        return q
Пример #7
0
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    # ---
    # Required Fields
    # ---
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(255), nullable=False)
    password = db.Column(db.String(255), nullable=False)
    salt = db.Column(db.String(8), nullable=False)
    joined = db.Column(db.TIMESTAMP(), default=datetime.datetime.utcnow)

    # ---
    # Public Profile Fields
    # ---
    company = db.Column(db.String(255))
    website = db.Column(db.String(255))
    location = db.Column(db.String(255))

    @classmethod
    def new(cls, username, email, password):
        u = cls()
        u.email = email.lower().strip()
        u.salt = cls._create_salt()
        u.password = cls._hash_password(password, u.salt)
        u.username = username.strip()
        return u

    @staticmethod
    def _create_salt():
        """
        Returns a new base64 salt.
        """
        return base64.b64encode(os.urandom(8))[:8]

    @staticmethod
    def _hash_password(password, salt):
        """
        Returns a hashed password from `password` and `salt`.
        """
        return hashlib.sha256(salt + password.strip()).hexdigest()

    def set_password(self, new_password):
        self.salt = self._create_salt()
        self.password = self._hash_password(new_password, self.salt)

    @classmethod
    def by_email(cls, email):
        return cls.query.filter_by(email=email.lower().strip()).first()

    @classmethod
    def by_username(cls, username):
        return cls.query.filter_by(username_i=username).first()

    @classmethod
    def email_exists(cls, email):
        return cls.query.filter_by(email=email.lower().strip()).count() >= 1

    @classmethod
    def username_exists(cls, username):
        return cls.query.filter_by(username_i=username).count() >= 1

    @classmethod
    def login(cls, username, password):
        """
        Returns a `User` object for which `username` and `password` are
        correct, otherwise ``None``.
        """
        u = cls.by_username(username)
        if u and u.password == cls._hash_password(password, u.salt):
            return u
        return None

    @hybrid_property
    def username_i(self):
        return self.username.lower()

    @username_i.comparator
    def username_i(cls):
        return CaseInsensitiveComparator(cls.username)

    def active_projects(self, limit=5):
        """
        Return this users most active projets (by descending message count).
        """
        q = self.projects.order_by(False).order_by('-message_count')
        q = q.limit(limit)
        return q

    def in_group(self, name):
        """
        Returns ``True`` if this user is in the group `name`, otherwise
        ``False``.
        """
        return any(g.name == name.lower() for g in self.groups)

    def add_group(self, name):
        """
        Adds this user to the group `name` if not already in it. The group
        will be created if needed.
        """
        if self.in_group(name):
            # We're already in this group.
            return

        self.groups.append(Group.get_or_create(name=name))

    def export(self):
        """
        Exports the user, his projects, and his hooks for use in a
        private-ly hosted Notifico instance.
        """
        j = {
            'user': {
                'username': self.username,
                'email': self.email,
                'joined': self.joined.isoformat(),
                'company': self.company,
                'website': self.website,
                'location': self.location
            },
            'projects': [{
                'name':
                p.name,
                'created':
                p.created.isoformat(),
                'public':
                p.public,
                'website':
                p.website,
                'message_count':
                p.message_count,
                'channels': [{
                    'created': c.created.isoformat(),
                    'channel': c.channel,
                    'host': c.host,
                    'port': c.port,
                    'ssl': c.ssl,
                    'public': c.public
                } for c in p.channels],
                'hooks': [{
                    'created': h.created.isoformat(),
                    'key': h.key,
                    'service_id': h.service_id,
                    'message_count': h.message_count,
                    'config': h.config
                } for h in p.hooks]
            } for p in self.projects]
        }

        return j