class OauthUserRoleEntity(db.Model, CRUDMixin):

    """ Map users to a role and partner """
    __tablename__ = 'oauth_user_role'

    id = db.Column(db.Integer, primary_key=True)
    partner_id = db.Column(
        db.Integer, db.ForeignKey('partner.partner_id'), nullable=False)
    user_id = db.Column(
        db.Integer, db.ForeignKey('oauth_user.id'), nullable=False)
    role_id = db.Column(
        db.Integer, db.ForeignKey('oauth_role.id'), nullable=False)
    added_at = db.Column(db.DateTime, nullable=False)

    # @OneToOne
    partner = db.relationship('PartnerEntity', uselist=False, lazy='joined',
                              foreign_keys=[partner_id])
    user = db.relationship('OauthUserEntity', uselist=False, lazy='joined')
    role = db.relationship('OauthRoleEntity', uselist=False, lazy='joined',
                           foreign_keys=[role_id])

    def __repr__(self):
        return "<UserRole(id: {0.id}, " \
            "partner_id: {0.partner_id}, " \
            "user_id: {0.user_id}, " \
            "user: {0.user.email}, " \
            "role_id: {0.role_id}, " \
            "role: {0.role.role_code}, " \
            "added_at: {0.added_at})>".format(self)
class OauthRoleEntity(db.Model, CRUDMixin):
    """
    Roles so far: root, admin, staff
    """
    __tablename__ = 'oauth_role'

    id = db.Column(db.Integer, primary_key=True)
    role_code = db.Column('role_code', db.Text, nullable=False)
    role_description = db.Column('role_description', db.Text, nullable=False)

    def __repr__(self):
        return "<OauthRoleEntity(id: {0.id}, " \
            "role_code: {0.role_code}, " \
            "role_description: {0.role_description})>".format(self)
Beispiel #3
0
class CRUDMixin(object):
    """ Helper class flask-sqlalchemy entities """
    __table_args__ = {'extend_existing': True}

    id = db.Column(db.Integer, primary_key=True)

    @classmethod
    def get_by_id(cls, id):
        if any(
            (isinstance(id, str) and id.isdigit(), isinstance(
                id, (int, float))), ):
            return cls.query.get(int(id))
        return None

    @classmethod
    def create(cls, **kwargs):
        """ Helper for session.add() + session.commit() """
        instance = cls(**kwargs)
        return instance.save()

    def update(self, commit=True, **kwargs):
        for attr, value in kwargs.items():
            setattr(self, attr, value)
        return self.save() if commit else self

    def save(self, commit=True):
        db.session.add(self)
        if commit:
            db.session.commit()
        return self

    def delete(self, commit=True):
        db.session.delete(self)
        return commit and db.session.commit()
Beispiel #4
0
class PartnerEntity(db.Model, CRUDMixin):
    """ Store partners sendig us the data """
    __tablename__ = 'partner'

    id = db.Column('partner_id', db.Integer, primary_key=True)
    partner_code = db.Column('partner_code', db.Text, nullable=False)
    partner_description = db.Column('partner_description',
                                    db.Text,
                                    nullable=False)
    partner_added_at = db.Column('partner_added_at',
                                 db.DateTime,
                                 nullable=False)

    def __repr__(self):
        """ Return a friendly object representation """
        return "<PartnerEntity(partner_id: {0.id}, "\
            "partner_code: {0.partner_code}, " \
            "partner_description: {0.partner_description}, " \
            "partner_addded_at: {0.partner_added_at})>".format(self)
Beispiel #5
0
class LinkageEntity(db.Model, CRUDMixin):
    """ Maps the UUIDs to "hashed chunks" """
    __tablename__ = 'linkage'

    id = db.Column('linkage_id', db.Integer, primary_key=True)
    partner_id = db.Column('partner_id',
                           db.Integer,
                           db.ForeignKey('partner.partner_id'),
                           nullable=False)
    linkage_uuid = db.Column('linkage_uuid', db.Binary, nullable=False)
    linkage_hash = db.Column('linkage_hash', db.Binary, nullable=False)
    linkage_addded_at = db.Column('linkage_added_at',
                                  db.DateTime,
                                  nullable=False)

    # @OneToOne
    partner = db.relationship(PartnerEntity, uselist=False, lazy='joined')

    @staticmethod
    def short_hash(val):
        return val[:8]

    @staticmethod
    def load_paginated(per_page=25, page_num=1):
        """
        Helper for formating a list of linkages
        """
        def item_from_entity(entity):
            return {
                'id': entity.id,
                'partner_code': entity.partner.partner_code,
                'added_at':
                entity.date_time.strftime(utils.FORMAT_US_DATE_TIME)
            }

        pagination = LinkageEntity.query.paginate(page_num, per_page, False)
        items = map(item_from_entity, pagination.items)
        return items, pagination.pages

    def friendly_uuid(self):
        return utils.hexlify(self.linkage_uuid)

    def friendly_hash(self):
        return utils.hexlify(self.linkage_hash)

    @staticmethod
    def get_chunks_cache(chunks):
        """
        From the list [x, y, z] of chunks return
        a dictionary which tells if a chunk was `linked` or not:
            {x: LinkageEntity, y: LinkageEntity, z: None}
        """
        bin_chunks = [
            binascii.unhexlify(chunk.encode('utf-8')) for chunk in chunks
        ]
        links = LinkageEntity.query.filter(
            LinkageEntity.linkage_hash.in_(bin_chunks)).all()
        links_cache = {link.friendly_hash(): link for link in links}

        result = {}
        for chunk in chunks:
            result[chunk] = links_cache.get(chunk, None)

        return result

    @staticmethod
    def get_distinct_uuids_for_chunks(chunks_cache):
        """
        From the list [x, y, z] of chunks return the set(uuid_1, uuid_2)
        if the database contains the following rows:
            x => uuid_1
            y => uuid_1
            z => uuid_2

        """
        result = set()

        for link in chunks_cache.values():
            if link:
                result.add(link.friendly_uuid())

        return result

    def __repr__(self):
        """ Return a friendly object representation """

        return "<LinkageEntity(linkage_id: {0.id}, "\
            "partner_id: {0.partner_id}, " \
            "linkage_uuid: {1}, "\
            "linkage_hash: {2}, "\
            "linkage_addded_at: {0.linkage_addded_at})>".format(
                self,
                binascii.hexlify(self.linkage_uuid),
                binascii.hexlify(self.linkage_hash)
            )
Beispiel #6
0
class OauthAccessTokenEntity(db.Model, CRUDMixin):
    """
    Access tokens are used for accessing protected data.
    """
    __tablename__ = 'oauth_access_token'

    id = db.Column(db.Integer, primary_key=True)
    client_id = db.Column(db.String(40),
                          db.ForeignKey(OauthClientEntity.client_id),
                          nullable=False)
    client = db.relationship(OauthClientEntity, uselist=False, lazy='joined')

    # currently only bearer is supported
    token_type = db.Column(db.String(40))
    access_token = db.Column(db.String(255), unique=True)
    refresh_token = db.Column(db.String(255), unique=True)

    # Note: this column stores UTC values
    expires = db.Column(db.DateTime)
    _scopes = db.Column(db.Text)
    added_at = db.Column('added_at', db.DateTime, nullable=False)

    @property
    def user(self):
        return self.client.user if self.client else None

    @property
    def scopes(self):
        if self._scopes:
            return self._scopes.split()
        return []

    def is_expired(self):
        """
        Note: If the `expires` attribute is None we consider the token expired.

        :rtype bool: true if 'expires' datetime > the current UTC datetime

        """
        if self.expires:
            return self.expires < datetime.utcnow()
        return True

    @property
    def expires_in(self):
        """
        :rtype int: the number of seconds left until the token expiration
        """
        secs = 0

        if not self.is_expired():
            diff = self.expires - datetime.utcnow()
            secs = int(diff.total_seconds()) + 1

        return secs

    def serialize(self):
        """
        It is very important to return at least the following three values:

            - id: used for lookups
            - access_token: used in the request "Authorization" header
            - expires_in: used to determine if we need to generate a new token

        """
        return {
            'id': self.id,
            'token_type': self.token_type,
            'access_token': self.access_token,
            'expires_in': self.expires_in,
            '.expires_on_utc': utils.serialize_date_utc(self.expires),
            '.expires_on_local': utils.serialize_date_est(self.expires),
        }

    def __repr__(self):
        """ Return a friendly object representation """
        return "<OauthAccessTokenEntity(id: {0.id}, "\
            "token_type: {0.token_type}, " \
            "user_id: {0.user.id}, " \
            "expires: {0.expires}, " \
            "expires_in: {0.expires_in}, " \
            "scopes: {0.scopes}, " \
            "added_at: {0.added_at})>".format(self)
class OauthUserEntity(db.Model, UserMixin, CRUDMixin):
    """
    A user, or resource owner, is usually the registered user on your site.
    """
    __tablename__ = 'oauth_user'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False)
    first_name = db.Column(db.String(255), nullable=True)
    last_name = db.Column(db.String(255), nullable=True)
    mi_name = db.Column(db.String(255), nullable=True)
    password_hash = db.Column(db.String(255), nullable=True)
    added_at = db.Column('added_at', db.DateTime, nullable=False)

    # Indirect mappings via `oauth_user_role` table
    partner = db.relationship(
        PartnerEntity,
        secondary=OauthUserRoleEntity.__tablename__,
        primaryjoin="oauth_user.c.id==oauth_user_role.c.user_id",
        secondaryjoin="oauth_user_role.c.partner_id==partner.c.partner_id",
        backref=db.backref('oauth_user'),
        uselist=False,
    )

    role = db.relationship(
        OauthRoleEntity,
        secondary=OauthUserRoleEntity.__tablename__,
        primaryjoin="oauth_user.c.id==oauth_user_role.c.user_id",
        secondaryjoin="oauth_user_role.c.role_id==oauth_role.c.id",
        backref=db.backref('oauth_user'),
        uselist=False,
    )

    def hash_password(self, password):
        """
        @see
            https://www.akkadia.org/drepper/SHA-crypt.txt
        """
        self.password_hash = pwd_context.encrypt(password,
                                                 scheme='sha512_crypt',
                                                 category='admin')

    def verify_password(self, password):
        """ Return true if password matches the stored hash """
        return pwd_context.verify(password,
                                  self.password_hash,
                                  scheme='sha512_crypt')

    # scheme='bcrypt')
    # scheme='bcrypt_sha256')
    # scheme='pbkdf2_sha512')

    # def is_authenticated(self):
    #     """ Returns True if the user is authenticated, i.e. they have provided
    #     valid credentials.
    #     """
    #     return True

    # def is_anonymous(self):
    #     """ Flag instances of valid users """
    #     return False

    def serialize(self):
        """ Helper for sending data in http responses """
        return {
            'id': self.id,
            'email': self.email,
            'added_at': self.added_at.isoformat(),
        }

    def __repr__(self):
        """ Return a friendly object representation """
        display_partner = self.partner.partner_code if self.partner else ''
        display_role = self.role.role_code if self.role else ''

        return "<UserEntity(id: {0.id}, "\
            "email: {0.email}, " \
            "partner_code: {1}, " \
            "role_code: {2}, " \
            "added_at: {0.added_at})>".format(
                self, display_partner, display_role)
class OauthClientEntity(db.Model, CRUDMixin):
    """
    A client is the app which want to use the resource of a user. It is
    suggested that the client is registered by a user on your site, but it is
    not required.
    """
    __tablename__ = 'oauth_client'

    id = db.Column(db.Integer, primary_key=True)
    client_id = db.Column(db.String(40), unique=True)
    client_secret = db.Column(db.String(55), nullable=False)

    user_id = db.Column(db.ForeignKey('oauth_user.id'))
    user = db.relationship(OauthUserEntity, uselist=False, lazy='joined')

    _redirect_uris = db.Column(db.Text)
    _default_scopes = db.Column(db.Text)
    added_at = db.Column('added_at', db.DateTime, nullable=False)

    @property
    def client_type(self):
        """
         confidential - an application that is capable of keeping a client
         password confidential to the world. This client password is assigned
         to the client app by the authorization server. This password is used
         to identify the client to the authorization server, to avoid fraud. An
         example of a confidential client could be a web app, where no one but
         the administrator can get access to the server, and see the client
         password.

         public - an application that is not capable of keeping a client
         password confidential. For instance, a mobile phone application or a
         desktop application that has the client password embedded inside it.
         Such an application could get cracked, and this could reveal the
         password. The same is true for a JavaScript application running in the
         users browser. The user could use a JavaScript debugger to look into
         the application, and see the client password
         """
        return 'confidential'

    @property
    def redirect_uris(self):
        if self._redirect_uris:
            return self._redirect_uris.split()
        return []

    @property
    def default_redirect_uri(self):
        # return self.redirect_uris[0]
        return ''

    @property
    def default_scopes(self):
        if self._default_scopes:
            return self._default_scopes.split()
        return []

    def serialize(self):
        return {
            'id': self.id,
            'user_id': self.user_id,
            'added_at': self.added_at.isoformat(),
        }

    def __repr__(self):
        """ Return a friendly object representation """
        return "<OauthClientEntity(id: {0.id}, "\
            "client_id: {0.client_id}, " \
            "user_id: {0.user_id}, " \
            "added_at: {0.added_at})>".format(self)