Example #1
0
class EmailRecord(db.Model, Timestamp):
    """
    OAuth2 Access Tokens storage model.
    """

    __tablename__ = 'email'

    guid = db.Column(db.GUID, default=uuid.uuid4, primary_key=True)  # pylint: disable=invalid-name

    recipient = db.Column(db.String, index=True, nullable=False)
    email_type = db.Column(db.Enum(EmailTypes), index=True, nullable=False)

    def __repr__(self):
        return ('<{class_name}('
                'guid={self.guid}, '
                'type={self.email_type}, '
                ')>'.format(class_name=self.__class__.__name__, self=self))
Example #2
0
class Subscription(db.Model, ModelMixin):
    # Come from Paddle
    cancel_url = db.Column(db.String(1024), nullable=False)
    update_url = db.Column(db.String(1024), nullable=False)
    subscription_id = db.Column(db.String(1024), nullable=False, unique=True)
    event_time = db.Column(ArrowType, nullable=False)
    next_bill_date = db.Column(db.Date, nullable=False)

    cancelled = db.Column(db.Boolean, nullable=False, default=False)

    plan = db.Column(db.Enum(PlanEnum), nullable=False)

    user_id = db.Column(
        db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
    )

    user = db.relationship(User)
class OAuth2Token(db.Model):
    """
    OAuth2 Access Tokens storage model.
    """

    __tablename__ = 'oauth2_token'

    id = db.Column(db.Integer, primary_key=True)  # pylint: disable=invalid-name
    client_id = db.Column(
        db.String(length=40),
        db.ForeignKey('oauth2_client.client_id'),
        index=True,
        nullable=False,
    )
    client = db.relationship('OAuth2Client')

    user_id = db.Column(db.ForeignKey('user.id', ondelete='CASCADE'),
                        index=True,
                        nullable=False)
    user = db.relationship('User')

    class TokenTypes(str, enum.Enum):
        # currently only bearer is supported
        Bearer = 'Bearer'

    token_type = db.Column(db.Enum(TokenTypes), nullable=False)

    access_token = db.Column(db.String(length=255),
                             unique=True,
                             nullable=False)
    refresh_token = db.Column(db.String(length=255),
                              unique=True,
                              nullable=True)
    expires = db.Column(db.DateTime, nullable=False)
    scopes = db.Column(ScalarListType(separator=' '), nullable=False)

    @classmethod
    def find(cls, access_token=None, refresh_token=None):
        if access_token:
            return cls.query.filter_by(access_token=access_token).first()
        elif refresh_token:
            return cls.query.filter_by(refresh_token=refresh_token).first()

    def delete(self):
        with db.session.begin():
            db.session.delete(self)
Example #4
0
class OAuth2Client(db.Model):
    """
    Model that binds OAuth2 Client ID and Secret to a specific User.
    """

    __tablename__ = 'oauth2_client'

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

    user_id = db.Column(db.ForeignKey('user.id', ondelete='CASCADE'),
                        index=True,
                        nullable=False)
    user = db.relationship(User)

    class ClientTypes(str, enum.Enum):
        public = 'public'
        confidential = 'confidential'

    client_type = db.Column(db.Enum(ClientTypes),
                            default=ClientTypes.public,
                            nullable=False)
    redirect_uris = db.Column(ScalarListType(separator=' '),
                              default=[],
                              nullable=False)
    default_scopes = db.Column(ScalarListType(separator=' '), nullable=False)

    @property
    def default_redirect_uri(self):
        redirect_uris = self.redirect_uris
        if redirect_uris:
            return redirect_uris[0]
        return None

    @classmethod
    def find(cls, client_id):
        if not client_id:
            return None
        return cls.query.get(client_id)

    def validate_scopes(self, scopes):
        # The only reason for this override is that Swagger UI has a bug which leads to that
        # `scope` parameter contains extra spaces between scopes:
        # https://github.com/frol/flask-restplus-server-example/issues/131
        return set(self.default_scopes).issuperset(set(scopes) - {''})
Example #5
0
class StockPrice(db.Model):
    # Define the table name
    __tablename__ = 'stocks_prices'

    # Set columns for the table
    ticker = db.Column('ticker', db.String, primary_key=True)
    date_time = db.Column('date_time', db.DateTime, primary_key=True)
    price = db.Column('price', db.Numeric)
    asset_type = db.Column('asset_type', db.Enum(AssetType))

    def as_dict(self):
        stock_price_as_dict = {
            'ticker': self.ticker,
            'date_time': self.date_time.strftime('%m-%d-%Y'),
            'price': str(self.price),
            'asset_type': self.asset_type.name
        }
        return stock_price_as_dict
Example #6
0
class Dish(db.Model):
    """Табличка блюда"""
    __tablename__ = 'dish'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String, nullable=False, unique=True)
    description = db.Column(db.String, nullable=False, unique=True)
    portion_count = db.Column(db.Integer,
                              CheckConstraint('portion_count>0'),
                              nullable=False)
    type_of_dish = db.Column(db.Enum(TypesOfDish), nullable=False)
    recipes = db.relationship('Recipe', backref='dish')
    ingredients = db.relationship('Ingredient',
                                  secondary=DISH_AND_INGREDIENT,
                                  backref=db.backref('dishes', lazy='dynamic'))

    def to_dict(self) -> dict:
        """Конвертирование данных о блюде в словарь"""
        dish_data = {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'portion_count': self.portion_count,
            'type_of_dish': self.type_of_dish
        }
        return dish_data

    def from_dict(self, data: dict):
        """Заполнение данных о блюде через словарь"""
        for field in ('name', 'description', 'portion_count', 'type_of_dish'):
            setattr(self, field, data.get(field))

    def __init__(self,
                 description=None,
                 name=None,
                 portion_count=None,
                 type_of_dish=None):
        self.description = description
        self.name = name
        self.portion_count = portion_count
        self.type_of_dish = type_of_dish

    def __repr__(self):
        return f"Dish({self.id}, {self.name}, {self.description}, {self.portion_count}, {self.type_of_dish})"
Example #7
0
class Collection(db.Model):
    __bind_key__ = 'olympics'
    __tablename__ = 'collection'

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(256), nullable=False)
    type = db.Column(db.Enum(*collection_enums),
                     nullable=False,
                     server_default="china_team")
    visible = db.Column(TINYINT,
                        nullable=False,
                        server_default=text(CollectionStatus.HIDDEN))
    display_order = db.Column(db.Integer,
                              nullable=False,
                              server_default=text('0'))
    image = db.Column(db.String(256), nullable=False)

    videos = db.relationship('OGVideo',
                             secondary='collection_video',
                             back_populates="collections")

    created_at = db.Column(db.TIMESTAMP,
                           server_default=db.func.current_timestamp(),
                           nullable=False)

    updated_at = db.Column(
        db.TIMESTAMP,
        server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
        nullable=False)

    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'visible': self.visible,
            'type': self.type,
            'image': add_image_domain(self.image),
            'imageCode': self.image,
            'created_at': self.created_at,
        }
Example #8
0
class User(UserMixin, db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64),
                         index=True,
                         unique=True,
                         nullable=False)
    email = db.Column(db.String(120), index=True, unique=True, nullable=False)
    registered_date = db.Column(db.String(120))
    if_verified = db.Column(db.Boolean())
    is_founder = db.Column(db.Boolean())
    real_name = db.Column(db.String(120))
    sex = db.Column(db.Enum(EnumGender), nullable=False)
    minority = db.Column(db.String(120))
    account_active = db.Column(db.Boolean())
    first_name = db.Column(db.String(120))
    urole = db.Column(db.String(140), default="normal")
    last_name = db.Column(db.String(120), nullable=False)
    password_hash = db.Column(db.String(128))

    complaints = db.relationship(Complaint, backref='User')
    comments = db.relationship(Comment, backref='User')

    def __init__(self, username, sex, registered_date):
        self.username = username
        self.sex = sex
        self.registered_date = registered_date

    def __repr__(self):
        return '<User {}>'.format(self.username)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    def get_urole(self):
        return self.urole
Example #9
0
class AppleSubscription(db.Model, ModelMixin):
    """
    For users who have subscribed via Apple in-app payment
    """

    user_id = db.Column(
        db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
    )

    expires_date = db.Column(ArrowType, nullable=False)

    # to avoid using "Restore Purchase" on another account
    original_transaction_id = db.Column(db.String(256), nullable=False, unique=True)
    receipt_data = db.Column(db.Text(), nullable=False)

    plan = db.Column(db.Enum(PlanEnum), nullable=False)

    user = db.relationship(User)

    def is_valid(self):
        # Todo: take into account grace period?
        return self.expires_date > arrow.now().shift(days=-_APPLE_GRACE_PERIOD_DAYS)
class OAuth2Client(db.Model):
    """
    Model that binds OAuth2 Client ID and Secret to a specific User.
    """

    __tablename__ = 'oauth2_client'

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

    user_id = db.Column(db.ForeignKey('user.id', ondelete='CASCADE'),
                        index=True,
                        nullable=False)
    user = db.relationship('User')

    class ClientTypes(str, enum.Enum):
        public = 'public'
        confidential = 'confidential'

    client_type = db.Column(db.Enum(ClientTypes),
                            default=ClientTypes.public,
                            nullable=False)
    redirect_uris = db.Column(ScalarListType(separator=' '),
                              default=[],
                              nullable=False)
    default_scopes = db.Column(ScalarListType(separator=' '), nullable=False)

    @property
    def default_redirect_uri(self):
        redirect_uris = self.redirect_uris
        if redirect_uris:
            return redirect_uris[0]
        return None

    @classmethod
    def find(cls, client_id):
        if not client_id:
            return
        return cls.query.get(client_id)
Example #11
0
class Rental(db.Model):
    """This is a rental class a customer can have one to
    many rentals and one to many books can be rented out manier times"""

    rental_id = db.Column(db.Integer,
                          primary_key=True,
                          nullable=False,
                          autoincrement=True)
    rental_charge_per_day = db.Column(db.Integer, nullable=False, default=1)
    min_rent_days = db.Column(db.Enum(MinRentDays, impl=db.String(10)),
                              nullable=False)
    issued_on = db.Column(db.DateTime, nullable=False)
    quantity = db.Column(db.Integer, nullable=False)
    returned_on = db.Column(db.DateTime, nullable=True)
    books = db.relationship('Book',
                            backref='book_rental',
                            uselist=False,
                            secondary=rentalitems)
    customer_id = db.Column(db.Integer, db.ForeignKey('customer.customer_id'))

    def __init__(self, *args, **kwargs):
        super(Rental, self).__init__(*args, **kwargs)
Example #12
0
class Turma(db.Model):
    __tablename__ = "turma"
    id = db.Column(db.Integer, primary_key=True)
    codigo = db.Column(db.String(20), nullable=False)
    geminada = db.Column(db.Boolean, default=False, nullable=False)
    aulas_num = db.Column(db.Integer, default=2, nullable=False)
    etapa = db.Column(db.Integer, nullable=False)
    periodo_letivo = db.Column(db.Enum(PeriodoLetivo), nullable=False)
    campus_id = db.Column(db.Integer,
                          db.ForeignKey('campus.id'),
                          nullable=False)
    professor_id = db.Column(db.Integer,
                             db.ForeignKey('professor.id'),
                             nullable=False)
    disciplina_id = db.Column(db.Integer,
                              db.ForeignKey('disciplina.id'),
                              nullable=False)
    indisponibilidade = db.relationship('TurmaIndisponibilidade',
                                        backref='turma',
                                        lazy=True)
    pre_agendado = db.relationship('TurmaPreAgendada',
                                   backref='turma',
                                   lazy=True)
Example #13
0
class Subscription(db.Model, ModelMixin):
    # Come from Paddle
    cancel_url = db.Column(db.String(1024), nullable=False)
    update_url = db.Column(db.String(1024), nullable=False)
    subscription_id = db.Column(db.String(1024), nullable=False, unique=True)
    event_time = db.Column(ArrowType, nullable=False)
    next_bill_date = db.Column(db.Date, nullable=False)

    cancelled = db.Column(db.Boolean, nullable=False, default=False)

    plan = db.Column(db.Enum(PlanEnum), nullable=False)

    user_id = db.Column(
        db.ForeignKey(User.id, ondelete="cascade"), nullable=False, unique=True
    )

    user = db.relationship(User)

    def plan_name(self):
        if self.plan == PlanEnum.monthly:
            return "Monthly ($2.99/month)"
        else:
            return "Yearly ($29.99/year)"
Example #14
0
class User(db.Model):
    """Class to represent a basic user that can hire vehicles

    :param username: The user's unique name to be identfied with and to log in
    :type username: string
    :param password: Hashed password for user to log in with
    :type password: string
    :param f_name: The user's first name
    :type f_name: string
    :param l_name: The user's last name
    :type l_name: string
    :param email: The user's email
    :type email: string
    :param role: The user's permission role
    :type role: app.model.user.Role
    :param google_credentials: Google authentication information
    :type username: credentials
    """
    username = db.Column(db.String(32), primary_key=True)
    password = db.Column(db.String(128), unique=False, nullable=False)
    f_name = db.Column(db.String(32), unique=False, nullable=False)
    l_name = db.Column(db.String(32), unique=False, nullable=False)
    email = db.Column(db.String(64), unique=False, nullable=False)
    role = db.Column(db.Enum(Role), unique=False, nullable=True)
    google_credentials = db.Column(db.PickleType(), unique=False, nullable=True)
    mac_address = db.Column(db.String(128), unique=True, nullable=True)
    pb_token = db.Column(db.String(128), unique=False, nullable=True)

    def __init__(self, username, password, f_name, l_name, email, role=Role.default):
        """Constructor method
        """
        self.username = username
        self.password = password
        self.f_name = f_name
        self.l_name = l_name
        self.email = email
        self.role = role
Example #15
0
class Event(db.Model):
    __tablename__ = 'event'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), nullable=False)
    alias = db.Column(db.String(32), nullable=False, server_default='')
    type = db.Column(db.Enum(*event_enums),
                     nullable=False,
                     server_default="misc")
    brief = db.Column(db.String(200), nullable=False, server_default='')
    iscup = db.Column(TINYINT,
                      nullable=False,
                      server_default=text(EventTypeStatus.NOTCUP))

    event_news = db.relationship('EventNews', backref='event', lazy='dynamic')
    match = db.relationship('Match', backref='event', lazy='dynamic')
    teams = db.relationship('Team',
                            secondary='event_team',
                            back_populates="events")

    activities = db.relationship('Activity', backref='event', lazy='dynamic')
    subpages = db.relationship('SubPage', backref='event', lazy='dynamic')

    created_at = db.Column(db.TIMESTAMP,
                           server_default=db.func.current_timestamp(),
                           nullable=False)
    updated_at = db.Column(
        db.TIMESTAMP,
        server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
        nullable=False)

    def to_dict(self):
        return dict(id=self.id,
                    name=self.name,
                    type=self.type,
                    iscup=self.iscup,
                    brief=self.brief)
Example #16
0
class FundingStatus(Funding):
    __tablename__ = 'funding_status'
    __table_args__: Tuple[ForeignKeyConstraint] = (db.ForeignKeyConstraint(
        ('funding_funding_id', 'funding_email', 'funding_code'),
        ['funding.funding_id', 'funding.email', 'funding.code'],
        ondelete='CASCADE',
        onupdate='CASCADE'), )

    funding_funding_id: int = db.Column(db.Integer,
                                        primary_key=True,
                                        nullable=False)
    funding_email: str = db.Column(db.String(50),
                                   primary_key=True,
                                   nullable=False)
    funding_code: str = db.Column(db.String(10),
                                  primary_key=True,
                                  nullable=False)
    balance: int = db.Column(db.Integer,
                             nullable=False,
                             server_default=db.FetchedValue())
    participants_num: int = db.Column(db.Integer,
                                      nullable=False,
                                      server_default=db.FetchedValue())
    status: Type[Enum] = db.Column(db.Enum(FundingStatusEnum))
Example #17
0
class User(UserMixin, ResourceMixin, db.Model):
    ROLE = OrderedDict([('member', 'Member'), ('admin', 'Admin')])

    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)

    # Relationships.
    credit_card = db.relationship(CreditCard,
                                  uselist=False,
                                  backref='users',
                                  lazy='subquery',
                                  passive_deletes=True)
    subscription = db.relationship(Subscription,
                                   uselist=False,
                                   lazy='subquery',
                                   backref='users',
                                   passive_deletes=True)
    app_authorization = db.relationship(AppAuthorization,
                                        uselist=False,
                                        backref='users',
                                        lazy='subquery',
                                        passive_deletes=True)
    base = db.relationship(Base,
                           uselist=False,
                           backref='users',
                           lazy='subquery',
                           passive_deletes=True)

    # Authentication.
    role = db.Column(db.Enum(*ROLE, name='role_types', native_enum=False),
                     index=True,
                     nullable=False,
                     server_default='member')
    active = db.Column('is_active',
                       db.Boolean(),
                       nullable=False,
                       server_default='1')
    username = db.Column(db.String(24), unique=True, index=True)
    email = db.Column(db.String(255),
                      unique=True,
                      index=True,
                      nullable=False,
                      server_default='')
    password = db.Column(db.String(128), nullable=False, server_default='')

    # Billing.
    name = db.Column(db.String(128), index=True)
    payment_id = db.Column(db.String(128), index=True)
    cancelled_subscription_on = db.Column(AwareDateTime())
    trial = db.Column('trial',
                      db.Boolean(),
                      nullable=False,
                      server_default='1')

    # Notifications
    send_failure_email = db.Column('send_failure_email',
                                   db.Boolean(),
                                   nullable=False,
                                   server_default='0')

    sign_in_count = db.Column(db.Integer, nullable=False, default=0)
    current_sign_in_on = db.Column(AwareDateTime())
    current_sign_in_ip = db.Column(db.String(45))
    last_sign_in_on = db.Column(AwareDateTime())
    last_sign_in_ip = db.Column(db.String(45))

    def __init__(self, **kwargs):
        # Call Flask-SQLAlchemy's constructor.
        super(User, self).__init__(**kwargs)

        self.password = User.encrypt_password(kwargs.get('password', ''))

    @classmethod
    def find_by_identity(cls, identity):
        """
        Find a user by their e-mail or username.

        :param identity: Email or username
        :type identity: str
        :return: User instance
        """
        return User.query.filter((User.email == identity)
                                 | (User.username == identity)).first()

    @classmethod
    def encrypt_password(cls, plaintext_password):
        """
        Hash a plaintext string using PBKDF2. This is good enough according
        to the NIST (National Institute of Standards and Technology).

        In other words while bcrypt might be superior in practice, if you use
        PBKDF2 properly (which we are), then your passwords are safe.

        :param plaintext_password: Password in plain text
        :type plaintext_password: str
        :return: str
        """
        if plaintext_password:
            return generate_password_hash(plaintext_password)

        return None

    @classmethod
    def deserialize_token(cls, token):
        """
        Obtain a user from de-serializing a signed token.

        :param token: Signed token.
        :type token: str
        :return: User instance or None
        """
        private_key = TimedJSONWebSignatureSerializer(
            current_app.config['SECRET_KEY'])
        try:
            decoded_payload = private_key.loads(token)

            return User.find_by_identity(decoded_payload.get('user_email'))
        except Exception:
            return None

    @classmethod
    def initialize_password_reset(cls, identity):
        """
        Generate a token to reset the password for a specific user.

        :param identity: User e-mail address or username
        :type identity: str
        :return: User instance
        """
        u = User.find_by_identity(identity)
        reset_token = u.serialize_token()

        # This prevents circular imports.
        from app.blueprints.user.tasks import (deliver_password_reset_email)
        deliver_password_reset_email.delay(u.id, reset_token)

        return u

    @classmethod
    def search(cls, query):
        """
        Search a resource by 1 or more fields.

        :param query: Search query
        :type query: str
        :return: SQLAlchemy filter
        """
        if not query:
            return ''

        search_query = '%{0}%'.format(query)
        search_chain = (User.email.ilike(search_query),
                        User.username.ilike(search_query))

        return or_(*search_chain)

    @classmethod
    def is_last_admin(cls, user, new_role, new_active):
        """
        Determine whether or not this user is the last admin account.

        :param user: User being tested
        :type user: User
        :param new_role: New role being set
        :type new_role: str
        :param new_active: New active status being set
        :type new_active: bool
        :return: bool
        """
        is_changing_roles = user.role == 'admin' and new_role != 'admin'
        is_changing_active = user.active is True and new_active is None

        if is_changing_roles or is_changing_active:
            admin_count = User.query.filter(User.role == 'admin').count()
            active_count = User.query.filter(User.is_active is True).count()

            if admin_count == 1 or active_count == 1:
                return True

        return False

    @classmethod
    def bulk_delete(cls, ids):
        """
        Override the general bulk_delete method because we need to delete them
        one at a time while also deleting them on Stripe.

        :param ids: List of ids to be deleted
        :type ids: list
        :return: int
        """
        delete_count = 0

        for id in ids:
            user = User.query.get(id)

            if user is None:
                continue

            if user.subscription is None:
                user.delete()
            else:
                subscription = Subscription()
                cancelled = subscription.cancel(user=user)

                # If successful, delete it locally.
                if cancelled:

                    user.delete()

            delete_count += 1

        return delete_count

    def is_active(self):
        """
        Return whether or not the user account is active, this satisfies
        Flask-Login by overwriting the default value.

        :return: bool
        """
        return self.active

    def get_auth_token(self):
        """
        Return the user's auth token. Use their password as part of the token
        because if the user changes their password we will want to invalidate
        all of their logins across devices. It is completely fine to use
        md5 here as nothing leaks.

        This satisfies Flask-Login by providing a means to create a token.

        :return: str
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = URLSafeTimedSerializer(private_key)
        data = [str(self.id), md5(self.password.encode('utf-8')).hexdigest()]

        return serializer.dumps(data)

    def authenticated(self, with_password=True, password=''):
        """
        Ensure a user is authenticated, and optionally check their password.

        :param with_password: Optionally check their password
        :type with_password: bool
        :param password: Optionally verify this as their password
        :type password: str
        :return: bool
        """
        if with_password:
            return check_password_hash(self.password, password)

        return True

    def serialize_token(self, expiration=3600):
        """
        Sign and create a token that can be used for things such as resetting
        a password or other tasks that involve a one off token.

        :param expiration: Seconds until it expires, defaults to 1 hour
        :type expiration: int
        :return: JSON
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = TimedJSONWebSignatureSerializer(private_key, expiration)
        return serializer.dumps({'user_email': self.email}).decode('utf-8')

    def update_activity_tracking(self, ip_address):
        """
        Update various fields on the user that's related to meta data on their
        account, such as the sign in count and ip address, etc..

        :param ip_address: IP address
        :type ip_address: str
        :return: SQLAlchemy commit results
        """
        self.sign_in_count += 1

        self.last_sign_in_on = self.current_sign_in_on
        self.last_sign_in_ip = self.current_sign_in_ip

        self.current_sign_in_on = datetime.datetime.now(pytz.utc)
        self.current_sign_in_ip = ip_address

        return self.save()
Example #18
0
class ApplicationUser(db.Model):
    __tablename__ = "application_user"
    id = db.Column(db.String(36), primary_key=True, default=str(uuid.uuid4()))
    user_id = db.Column(
        db.String(36),
        db.ForeignKey("user.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    username = db.Column(db.String(255), nullable=False, unique=True)
    password = db.Column(db.String(255), nullable=False)
    user_type = db.Column(db.Enum(UserType),
                          server_default="USER",
                          nullable=False)
    user = db.relationship("User", foreign_keys=user_id, lazy="select")

    def __init__(self, user_id, username, password, user_type: UserType):
        self.user_id = user_id
        self.username = username
        self.password = generate_password_hash(password,
                                               method="sha256",
                                               salt_length=8)
        self.user_type = user_type

    def save_to_db(self):
        """
        Saves Application User to Database

        Returns:

        """
        try:
            db.session.add(self)
            db.session.commit()
            return True
        except:
            db.session.rollback()
            raise

    @classmethod
    def authenticate(cls, username: str, password: str):
        """
        Authenticate function.

        Args:
            username: Represents the username
            password: Represents the password

        Returns:
            ApplicationUser object.
            Account details are accessible by calling "user" property of this result

            Ex:

            account =  ApplicationUser.authenticate(username='******', password='******')
            if not account:
                print("Credentials incorrect or user does not exists")
            else:
                account_details = account.user

        """

        if not username or not password:
            return None

        account = cls.query.filter_by(username=username).first()
        if not account or not check_password_hash(account.password, password):
            return None

        return account
Example #19
0
class Code(db.Model, HoustonModel):
    """
    OAuth2 Access Tokens storage model.
    """

    guid = db.Column(
        db.GUID, default=uuid.uuid4, primary_key=True
    )  # pylint: disable=invalid-name

    user_guid = db.Column(db.GUID, db.ForeignKey('user.guid'), index=True, nullable=False)
    user = db.relationship(
        'User', backref=db.backref('codes', cascade='delete, delete-orphan')
    )

    code_type = db.Column(db.Enum(CodeTypes), index=True, nullable=False)

    accept_code = db.Column(db.String(length=64), index=True, unique=True, nullable=False)
    reject_code = db.Column(db.String(length=64), index=True, unique=True, nullable=False)

    expires = db.Column(db.DateTime, nullable=False)
    response = db.Column(db.DateTime, nullable=True)

    decision = db.Column(db.Enum(CodeDecisions), nullable=True)

    # __table_args__ = (
    #     db.UniqueConstraint(user_guid, code_type),
    # )

    def __repr__(self):
        return (
            '<{class_name}('
            'guid={self.guid}, '
            'created={self.created}, '
            'type={self.code_type}, '
            'accept={self.accept_code}, '
            'reject={self.reject_code}, '
            'expires={self.expires}, '
            'is_expired={self.is_expired}, '
            'is_resolved={self.is_resolved}, '
            ')>'.format(class_name=self.__class__.__name__, self=self)
        )

    @classmethod
    def generate(cls, length=8):
        new_code = []
        while len(new_code) < length:
            candidate = random.choice(CODE_VALID_CHARACTERS)
            new_code.append(candidate)
        new_code = ''.join(new_code)
        return new_code

    @classmethod
    def get(
        cls,
        user,
        code_type,
        create=True,
        replace=False,
        replace_ttl=12 * 60,
        create_force=False,
    ):
        """ Replace will automatically invalidate any codes previously issued (above the replace_ttl value, in minutes)"""
        code_settings = CODE_SETTINGS.get(code_type, None)
        assert code_settings is not None, 'Code type was unrecognized: %r' % (code_type,)

        # Get any codes that fit this request
        existing_codes = (
            cls.query.filter_by(user=user, code_type=code_type)
            .order_by(cls.created.desc())
            .all()
        )
        valid_codes = [
            code
            for code in existing_codes
            if not code.is_expired and not code.is_resolved
        ]

        if replace:
            # Create a new code and delete any previous codes in the process
            if len(valid_codes) > 0:
                if replace_ttl is None:
                    delete_codes = valid_codes
                else:
                    replace_ttl_date = datetime.datetime.now(
                        tz=pytz.utc
                    ) - datetime.timedelta(minutes=replace_ttl)

                    delete_codes = [
                        valid_code
                        for valid_code in valid_codes
                        if valid_code.created.replace(tzinfo=pytz.utc) < replace_ttl_date
                    ]
                log.warning('Replacing codes, deleting %d' % (len(delete_codes),))
                with db.session.begin():
                    for delete_code in delete_codes:
                        db.session.delete(delete_code)

                # We have deleted all of the matching codes (respecting replace_ttl_minutes), re-run this function
                # It is possible to have sent an email confirmation code in the past hour and get it back
                return cls.get(user, code_type, replace=False, replace_ttl=replace_ttl)

        code = None
        if len(valid_codes) > 0:
            # Take the most recently generated valid code
            code = valid_codes[0]

        if code is None and create or create_force:
            # Create a new code
            now = datetime.datetime.now(tz=current_app.config.get('TIMEZONE'))

            while True:
                accept_code = cls.generate(code_settings.get('len'))
                reject_code = cls.generate(code_settings.get('len'))
                existing_accept_codes = cls.find(accept_code)
                existing_reject_codes = cls.find(reject_code)
                if len(existing_accept_codes) == 0 and len(existing_reject_codes) == 0:
                    # Found unique codes
                    break
                log.warning('Finding alternate codes, there was a conflict:')
                log.warning('\tCandidate   Accept Code  : %r' % (accept_code,))
                log.warning('\tConflicting Accept Codes : %r' % (existing_accept_codes,))
                log.warning('\tCandidate   Reject Code  : %r' % (reject_code,))
                log.warning('\tConflicting Reject Codes : %r' % (existing_reject_codes,))

            ttl_days = code_settings.get('ttl')
            if ttl_days is None or ttl_days < 0:
                ttl_minutes = TTL_MINUTE_DEFAULT if ttl_days is None else 0
                expires_ = now + datetime.timedelta(minutes=ttl_minutes)
                expires = datetime.datetime(
                    expires_.year,
                    expires_.month,
                    expires_.day,
                    expires_.hour,
                    expires_.minute,
                    expires_.second,
                    tzinfo=current_app.config.get('TIMEZONE'),
                )
            else:
                # Round up to (midnight - 1 second) of the TTL day
                expires_ = now + datetime.timedelta(days=ttl_days)
                expires = datetime.datetime(
                    expires_.year,
                    expires_.month,
                    expires_.day,
                    23,
                    59,
                    59,
                    tzinfo=current_app.config.get('TIMEZONE'),
                )

            expires_utc = expires.astimezone(pytz.utc)
            code_kwargs = {
                'user_guid': user.guid,
                'code_type': code_type,
                'accept_code': accept_code,
                'reject_code': reject_code,
                'expires': expires_utc,
            }
            with db.session.begin():
                code = Code(**code_kwargs)
                db.session.add(code)
            db.session.refresh(code)
            db.session.refresh(user)

        return code

    @classmethod
    def find(cls, code):
        matched_codes = (
            Code.query.filter(
                or_(
                    Code.accept_code == code,
                    Code.reject_code == code,
                )
            )
            .order_by(cls.created.desc())
            .all()
        )
        return matched_codes

    @classmethod
    def cleanup(cls):
        codes = cls.query.all()
        old_codes = [code for code in codes if code.is_expired and not code.is_resolved]
        log.warning('Cleaning codes, deleting %d' % (len(old_codes),))
        with db.session.begin():
            for old_code in old_codes:
                db.session.delete(old_code)

        return None

    @classmethod
    def received(cls, code_str):
        # Strip code, keeping only valid characters

        if code_str is None:
            return CodeDecisions.error, None

        code_str = code_str.upper()
        code_str = ''.join([char for char in code_str if char in CODE_VALID_CHARACTERS])

        code_list = Code.find(code_str)
        if len(code_list) == 0:
            decision = CodeDecisions.unknown
            code = None
        elif len(code_list) > 1:
            decision = CodeDecisions.error
            code = code_list
        else:
            code = code_list[0]

            if code.is_expired:
                decision = CodeDecisions.expired
            elif code.is_resolved:
                decision = CodeDecisions.dismiss
            elif code_str in [code.accept_code]:
                decision = CodeDecisions.accept
                code.record(decision)
            elif code_str in [code.reject_code]:
                decision = CodeDecisions.reject
                code.record(decision)
            else:
                decision = CodeDecisions.error

        return decision, code

    @property
    def is_expired(self):
        now_utc = datetime.datetime.now(tz=pytz.utc)
        expired = now_utc > self.expires.replace(tzinfo=pytz.utc)
        return expired

    @property
    def is_resolved(self):
        resolved = self.response is not None
        return resolved

    def record(self, decision):
        self.decision = decision
        self.response = datetime.datetime.now(tz=pytz.utc)
        with db.session.begin():
            db.session.merge(self)
        db.session.refresh(self)
        return self

    def delete(self):
        with db.session.begin():
            db.session.delete(self)
Example #20
0
class OAuth2Token(db.Model):
    """
    OAuth2 Access Tokens storage model.
    """

    __tablename__ = 'oauth2_token'

    guid = db.Column(
        db.GUID, default=uuid.uuid4, primary_key=True
    )  # pylint: disable=invalid-name

    client_guid = db.Column(
        db.GUID,
        db.ForeignKey('oauth2_client.guid'),
        index=True,
        nullable=False,
    )
    client = db.relationship('OAuth2Client')

    user_guid = db.Column(
        db.ForeignKey('user.guid', ondelete='CASCADE'), index=True, nullable=False
    )
    user = db.relationship('User')

    class TokenTypes(str, enum.Enum):
        # currently only bearer is supported
        Bearer = 'Bearer'

    token_type = db.Column(db.Enum(TokenTypes), nullable=False)

    access_token = db.Column(
        db.String(length=128),
        default=security.generate_random_128,
        unique=True,
        nullable=False,
    )
    refresh_token = db.Column(
        db.String(length=128),
        default=security.generate_random_128,
        unique=True,
        nullable=True,
    )
    expires = db.Column(db.DateTime, nullable=False)
    scopes = db.Column(ScalarListType(separator=' '), nullable=False)

    @property
    def client_id(self):
        return self.client_guid

    @classmethod
    def find(cls, access_token=None, refresh_token=None):
        response = None

        if access_token:
            response = cls.query.filter_by(access_token=access_token).first()

        if refresh_token:
            response = cls.query.filter_by(refresh_token=refresh_token).first()

        return response

    def delete(self):
        with db.session.begin():
            db.session.delete(self)

    @property
    def is_expired(self):
        now_utc = datetime.datetime.now(tz=pytz.utc)
        expired = now_utc > self.expires.replace(tzinfo=pytz.utc)
        return expired
Example #21
0
class Lesson(db.Model):
    __tablename__ = "lesson"
    __table_args__ = {"extend_existing": True}
    id = db.Column(db.String(36), primary_key=True, default=str(uuid.uuid4()))
    name = db.Column(db.String(255), nullable=False)
    subname = db.Column(db.String(255), nullable=False)
    duration = db.Column(db.String(255), nullable=False)
    status = db.Column(db.Enum(LessonStatus), server_default="DRAFT", nullable=False)
    updated_at = db.Column(db.DateTime, nullable=True)
    created_at = db.Column(db.DateTime, nullable=False)
    users = db.relationship("LessonUser", back_populates="lesson")

    # Cards of this lesson
    cards = db.relationship(
        "Card", primaryjoin="Card.lesson_id==Lesson.id", lazy="joined"
    )

    def __init__(self, name, subname, duration, status: LessonStatus):
        self.name = name
        self.subname = subname
        self.duration = duration
        self.status = status

    def save_to_db(self):
        """
        Saves Lesson to Database

        Returns:

        """
        try:
            self.updated_at = datetime.datetime.utcnow()
            self.created_at = datetime.datetime.utcnow()

            db.session.add(self)
            db.session.commit()
            return True
        except:
            db.session.rollback()
            raise

    def update(self):
        """
        Updates object

        Returns:

        """
        try:
            self.updated_at = datetime.datetime.utcnow()
            db.session.commit()
        except:
            db.session.rollback()
            raise

    @classmethod
    def find_by_id(cls, id):
        """
        Find Lesson by ID

        Args:
            id: user uuid

        Returns:
            User object

        """
        result = cls.query.filter_by(id=id).scalar()
        if result:
            return result
        else:
            return None

    def delete_self(self):
        """
        Deletes itself
        Returns:

        """
        try:
            db.session.delete(self)
            db.session.commit()
            return True
        except:
            raise

    @classmethod
    def delete_by_id(cls, id):
        """
        Deletes lesson by ID
        Args:
            id:

        Returns:

        """
        lesson = cls.query.filter_by(id=id).scalar()
        if lesson:
            return lesson.delete_self()
        else:
            return False
Example #22
0
class Submission(db.Model, HoustonModel):
    """
    Submission database model.

    Submission Structure:
        _db/submissions/<submission GUID>/
            - .git/
            - _submission/
            - - <user's uploaded data>
            - _assets/
            - - <symlinks into _submission/ folder> with name <asset GUID >.ext --> ../_submissions/path/to/asset/original_name.ext
            - metadata.json
    """

    guid = db.Column(db.GUID, default=uuid.uuid4, primary_key=True)  # pylint: disable=invalid-name

    major_type = db.Column(
        db.Enum(SubmissionMajorType),
        default=SubmissionMajorType.unknown,
        index=True,
        nullable=False,
    )

    commit = db.Column(db.String(length=40), nullable=True, unique=True)
    commit_mime_whitelist_guid = db.Column(db.GUID, index=True, nullable=True)
    commit_houston_api_version = db.Column(db.String,
                                           index=True,
                                           nullable=True)

    description = db.Column(db.String(length=255), nullable=True)

    meta = db.Column(db.JSON, nullable=True)

    owner_guid = db.Column(db.GUID,
                           db.ForeignKey('user.guid'),
                           index=True,
                           nullable=False)
    owner = db.relationship('User', backref=db.backref('submissions'))

    def __repr__(self):
        return ('<{class_name}('
                'guid={self.guid}, '
                ')>'.format(class_name=self.__class__.__name__, self=self))

    def ensure_repository(self):
        return current_app.sub.ensure_repository(self)

    def get_repository(self):
        return current_app.sub.get_repository(self)

    def git_write_upload_file(self, upload_file):
        repo = self.get_repository()
        file_repo_path = os.path.join(repo.working_tree_dir, '_submission',
                                      upload_file.filename)
        upload_file.save(file_repo_path)
        log.info('Wrote file upload and added to local repo: %r' %
                 (file_repo_path, ))

    def git_copy_path(self, path):
        import shutil

        absolute_path = os.path.abspath(os.path.expanduser(path))
        if not os.path.exists(path):
            raise IOError('The path %r does not exist.' % (absolute_path, ))

        repo = self.get_repository()
        repo_path = os.path.join(repo.working_tree_dir, '_submission')

        absolute_path = absolute_path.rstrip('/')
        repo_path = repo_path.rstrip('/')
        absolute_path = '%s/' % (absolute_path, )
        repo_path = '%s/' % (repo_path, )

        if os.path.exists(repo_path):
            shutil.rmtree(repo_path)

        shutil.copytree(absolute_path, repo_path)

    def git_commit(self, message, realize=True, update=True):
        repo = self.get_repository()

        if realize:
            self.realize_submission()

        if update:
            self.update_asset_symlinks()

        submission_path = self.get_absolute_path()
        submission_metadata_path = os.path.join(submission_path,
                                                'metadata.json')

        assert os.path.exists(submission_metadata_path)
        with open(submission_metadata_path, 'r') as submission_metadata_file:
            submission_metadata = json.load(submission_metadata_file)

        submission_metadata['commit_mime_whitelist_guid'] = str(
            current_app.sub.mime_type_whitelist_guid)
        submission_metadata['commit_houston_api_version'] = str(version)

        with open(submission_metadata_path, 'w') as submission_metadata_file:
            json.dump(submission_metadata, submission_metadata_file)

        # repo.index.add('.gitignore')
        repo.index.add('_assets/')
        repo.index.add('_submission/')
        repo.index.add('metadata.json')

        commit = repo.index.commit(message)

        self.update_metadata_from_commit(commit)

    def git_push(self):
        repo = self.get_repository()
        assert repo is not None

        with GitLabPAT(repo):
            log.info('Pushing to authorized URL')
            repo.git.push('--set-upstream', repo.remotes.origin, repo.head.ref)
            log.info('...pushed to %s' % (repo.head.ref, ))

        return repo

    def git_pull(self):
        repo = self.get_repository()
        assert repo is not None

        with GitLabPAT(repo):
            log.info('Pulling from authorized URL')
            repo.git.pull(repo.remotes.origin, repo.head.ref)
            log.info('...pulled')

        self.update_metadata_from_repo(repo)

        return repo

    def git_clone(self, project):
        repo = self.get_repository()
        assert repo is None

        submission_abspath = self.get_absolute_path()
        gitlab_url = project.web_url

        with GitLabPAT(url=gitlab_url) as glpat:
            args = (
                gitlab_url,
                submission_abspath,
            )
            log.info('Cloning remote submission:\n\tremote: %r\n\tlocal:  %r' %
                     args)
            glpat.repo = git.Repo.clone_from(glpat.authenticated_url,
                                             submission_abspath)
            log.info('...cloned')

        repo = self.get_repository()
        assert repo is not None

        self.update_metadata_from_project(project)
        self.update_metadata_from_repo(repo)

        # Traverse the repo and create Asset objects in database
        self.update_asset_symlinks()

        return repo

    def realize_submission(self):
        """
        Unpack any archives and resolve any symlinks

        Must check for security vulnerabilities around decompression bombs and
        recursive links
        """
        ARCHIVE_MIME_TYPE_WHITELIST = [  # NOQA
            'application/gzip',
            'application/vnd.rar',
            'application/x-7z-compressed',
            'application/x-bzip',
            'application/x-bzip2',
            'application/x-tar',
            'application/zip',
        ]
        pass

    def update_asset_symlinks(self, verbose=True):
        """
        Traverse the files in the _submission/ folder and add/update symlinks
        for any relevant files we identify

        Ref:
            https://pypi.org/project/python-magic/
            https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
            http://www.iana.org/assignments/media-types/media-types.xhtml
        """
        from app.modules.assets.models import Asset
        import utool as ut
        import magic

        submission_abspath = self.get_absolute_path()
        submission_path = os.path.join(submission_abspath, '_submission')
        assets_path = os.path.join(submission_abspath, '_assets')

        current_app.sub.ensure_initialed()

        # Walk the submission path, looking for white-listed MIME type files
        files = []
        skipped = []
        errors = []
        walk_list = sorted(list(os.walk(submission_path)))
        print('Walking submission...')
        for root, directories, filenames in tqdm.tqdm(walk_list):
            filenames = sorted(filenames)
            for filename in filenames:
                filepath = os.path.join(root, filename)

                # Normalize path (sanity check)
                filepath = os.path.normpath(filepath)

                # Sanity check, ensure that the path is formatted well
                assert os.path.exists(filepath)
                assert os.path.isabs(filepath)
                try:
                    basename = os.path.basename(filepath)
                    _, extension = os.path.splitext(basename)
                    extension = extension.lower()
                    extension = extension.strip('.')

                    if basename.startswith('.'):
                        # Skip hidden files
                        if basename not in ['.touch']:
                            skipped.append((filepath, basename))
                        continue

                    if os.path.isdir(filepath):
                        # Skip any directories (sanity check)
                        skipped.append((filepath, extension))
                        continue

                    if os.path.islink(filepath):
                        # Skip any symbolic links (sanity check)
                        skipped.append((filepath, extension))
                        continue

                    mime_type = magic.from_file(filepath, mime=True)
                    if mime_type not in current_app.sub.mime_type_whitelist:
                        # Skip any unsupported MIME types
                        skipped.append((filepath, extension))
                        continue

                    magic_signature = magic.from_file(filepath)
                    size_bytes = os.path.getsize(filepath)

                    file_data = {
                        'filepath': filepath,
                        'path': basename,
                        'extension': extension,
                        'mime_type': mime_type,
                        'magic_signature': magic_signature,
                        'size_bytes': size_bytes,
                        'submission_guid': self.guid,
                    }
                    files.append(file_data)
                except Exception:
                    logging.exception('Got exception in update_asset_symlinks')
                    errors.append(filepath)

        if verbose:
            print('Processed asset files from submission: %r' % (self, ))
            print('\tFiles   : %d' % (len(files), ))
            print('\tSkipped : %d' % (len(skipped), ))
            if len(skipped) > 0:
                skipped_ext_list = [skip[1] for skip in skipped]
                skipped_ext_str = ut.repr3(ut.dict_hist(skipped_ext_list))
                skipped_ext_str = skipped_ext_str.replace('\n', '\n\t\t')
                print('\t\t%s' % (skipped_ext_str, ))
            print('\tErrors  : %d' % (len(errors), ))

        # Compute the xxHash64 for all found files
        filepath_list = [file_data['filepath'] for file_data in files]
        arguments_list = list(zip(filepath_list))
        print('Computing filesystem xxHash64...')
        filesystem_xxhash64_list = parallel(compute_xxhash64_digest_filepath,
                                            arguments_list)
        filesystem_guid_list = list(
            map(ut.hashable_to_uuid, filesystem_xxhash64_list))

        # Update file_data with the filesystem and semantic hash information
        zipped = zip(files, filesystem_xxhash64_list, filesystem_guid_list)
        for file_data, filesystem_xxhash64, filesystem_guid in zipped:
            file_data['filesystem_xxhash64'] = filesystem_xxhash64
            file_data['filesystem_guid'] = filesystem_guid
            semantic_guid_data = (
                file_data['submission_guid'],
                file_data['filesystem_guid'],
            )
            file_data['semantic_guid'] = ut.hashable_to_uuid(
                semantic_guid_data)

        # Delete all existing symlinks
        existing_filepath_guid_mapping = {}
        existing_asset_symlinks = ut.glob(os.path.join(assets_path, '*'))
        for existing_asset_symlink in existing_asset_symlinks:
            basename = os.path.basename(existing_asset_symlink)
            if basename in ['.touch', 'derived']:
                continue
            existing_asset_target = os.readlink(existing_asset_symlink)
            existing_asset_target_ = os.path.abspath(
                os.path.join(assets_path, existing_asset_target))
            if os.path.exists(existing_asset_target_):
                uuid_str, _ = os.path.splitext(basename)
                uuid_str = uuid_str.strip().strip('.')
                try:
                    existing_filepath_guid_mapping[
                        existing_asset_target_] = uuid.UUID(uuid_str)
                except Exception:
                    pass
            os.remove(existing_asset_symlink)

        # Add new or update any existing Assets found in the Submission
        asset_submission_filepath_list = [
            file_data.pop('filepath', None) for file_data in files
        ]
        assets = []
        with db.session.begin():
            for file_data, asset_submission_filepath in zip(
                    files, asset_submission_filepath_list):
                semantic_guid = file_data.get('semantic_guid', None)
                asset = Asset.query.filter(
                    Asset.semantic_guid == semantic_guid).first()
                if asset is None:
                    # Check if we can recycle existing GUID from symlink
                    recycle_guid = existing_filepath_guid_mapping.get(
                        asset_submission_filepath, None)
                    if recycle_guid is not None:
                        file_data['guid'] = recycle_guid
                    # Create record if asset is new
                    asset = Asset(**file_data)
                    db.session.add(asset)
                else:
                    # Update record if Asset exists
                    for key in file_data:
                        if key in [
                                'submission_guid', 'filesystem_guid',
                                'semantic_guid'
                        ]:
                            continue
                        value = file_data[key]
                        setattr(asset, key, value)
                    db.session.merge(asset)
                assets.append(asset)

        # Update all symlinks for each Asset
        for asset, asset_submission_filepath in zip(
                assets, asset_submission_filepath_list):
            db.session.refresh(asset)
            asset.update_symlink(asset_submission_filepath)
            if verbose:
                print(filepath)
                print('\tAsset         : %s' % (asset, ))
                print('\tSemantic GUID : %s' % (asset.semantic_guid, ))
                print('\tExtension     : %s' % (asset.extension, ))
                print('\tMIME type     : %s' % (asset.mime_type, ))
                print('\tSignature     : %s' % (asset.magic_signature, ))
                print('\tSize bytes    : %s' % (asset.size_bytes, ))
                print('\tFS xxHash64   : %s' % (asset.filesystem_xxhash64, ))
                print('\tFS GUID       : %s' % (asset.filesystem_guid, ))

        # Get all historical and current Assets for this Submission
        db.session.refresh(self)

        # Delete any historical Assets that have been deleted from this commit
        deleted_assets = list(set(self.assets) - set(assets))
        if verbose:
            print('Deleting %d orphaned Assets' % (len(deleted_assets), ))
        with db.session.begin():
            for deleted_asset in deleted_assets:
                deleted_asset.delete()
        db.session.refresh(self)

    def update_metadata_from_project(self, project):
        # Update any local metadata from sub
        for tag in project.tag_list:
            tag = tag.strip().split(':')
            if len(tag) == 2:
                key, value = tag
                key_ = key.lower()
                value_ = value.lower()
                if key_ == 'type':
                    default_major_type = SubmissionMajorType.unknown
                    self.major_type = getattr(SubmissionMajorType, value_,
                                              default_major_type)

        self.description = project.description
        with db.session.begin():
            db.session.merge(self)
        db.session.refresh(self)

    def update_metadata_from_repo(self, repo):
        repo = self.get_repository()
        assert repo is not None

        if len(repo.branches) > 0:
            commit = repo.branches.master.commit
            self.update_metadata_from_commit(commit)

        return repo

    def update_metadata_from_commit(self, commit):
        with db.session.begin():
            self.commit = commit.hexsha

            metadata_path = os.path.join(commit.repo.working_dir,
                                         'metadata.json')
            assert os.path.exists(metadata_path)
            with open(metadata_path, 'r') as metadata_file:
                metadata_dict = json.load(metadata_file)

            self.commit_mime_whitelist_guid = metadata_dict.get(
                'commit_mime_whitelist_guid',
                current_app.sub.mime_type_whitelist_guid)
            self.commit_houston_api_version = metadata_dict.get(
                'commit_houston_api_version', version)

            db.session.merge(self)
        db.session.refresh(self)

    def get_absolute_path(self):
        submissions_database_path = current_app.config.get(
            'SUBMISSIONS_DATABASE_PATH', None)
        assert submissions_database_path is not None
        assert os.path.exists(submissions_database_path)

        submission_path = os.path.join(submissions_database_path,
                                       str(self.guid))

        return submission_path

    def delete(self):
        for asset in self.assets:
            asset.delete()
        db.session.refresh(self)
        with db.session.begin():
            db.session.delete(self)
Example #23
0
class User(db.Model, UserMixin, ResourceMixin):

    __tablename__ = "users"
    ROLE = OrderedDict()
    ROLE["member"] = "MEMBER"
    ROLE["admin"] = "ADMIN"

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

    #Authentication

    role = db.Column(
        db.Enum(*ROLE,
                name="role_types",
                native_enum=False,
                server_default="member",
                index=True,
                nullable=False))
    active = db.Column("is_active",
                       db.Boolean,
                       server_default="1",
                       nullable=False)
    username = db.Column(db.String(30), unique=True, index=True)
    email = db.Column(db.String(128), unique=True, index=True, nullable=False)
    password = db.Column(db.String(30), nullable=False, server_default='')
    subscription = db.relationship(Subscription,
                                   backref="users",
                                   uselist=False,
                                   passive_deletes=True)
    creditcard = db.relationship(CreditCard,
                                 backref='users',
                                 passive_deletes=True,
                                 uselist=False)

    # Activity Tracking

    sign_in_count = db.Column(db.Integer, nullable=False, default=0)
    current_sign_in_on = db.Column(AwareDateTime())
    current_sign_in_ip = db.Column(db.String(45))
    last_sign_is_on = db.Column(AwareDateTime())

    #Personal Details
    name = db.Column("name", db.String(128))
    payment_id = db.Column(db.String(128), unique=True)
    cancelled_subscription_on = db.Column(AwareDateTime())

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        self.password = User.encrypt_password(kwargs.get("password", " "))

    @classmethod
    def find_by_identity(cls, identity):
        """Find a User by their e-mail or Username.
        :param identity: E-mail or Username
        :type identity: str
        :return: User instance
        """
        current_app.logger.debug("{0} has tried to login".format(identity))
        return User.query.filter((User.email == identity)
                                 | (User.username == identity)).first()

    @classmethod
    def encrypt_password(cls, password_plainText):
        if password_plainText:
            generate_password_hash(password_plainText)
        return None

    def authenicate_password(self, password='', with_password=True):
        """The check password Hash method accepts the Password Hash and the password
        and checks if its the correct password Hash for the current password if the hash matches
        then it returns true"""

        if with_password:
            check_password_hash(self.password, password)

        return True

    def get_password_reset_token(self, expires_in=600):

        token = jwt.encode(
            {
                "user_id": self.id,
                "role": self.role,
                "username": self.username,
                "exp": time() + expires_in
            },
            current_app.config["SECRET_KEY"],
            algorithm="HS256").decode("UTF-8")
        print(token)
        return token

    @staticmethod
    def decode_password_reset_token(self, token):
        payload = jwt.decode(token,
                             current_app.config["SECRET_KEY"],
                             algorithms=["HS256"])["reset_password"]
        return payload

    @classmethod
    def search(cls, query):
        """
        Search a resource by 1 or more fields.

        :param query: Search query
        :type query: str
        :return: SQLAlchemy filter
        """
        if not query:
            return ''

        search_query = '%{0}%'.format(query)
        search_chain = (User.email.ilike(search_query),
                        User.username.ilike(search_query))

        return or_(*search_chain)

    @classmethod
    def is_last_admin(cls, user, new_role, new_active):
        """
        Determine whether or not this user is the last admin account.

        :param user: User being tested
        :type user: User
        :param new_role: New role being set
        :type new_role: str
        :param new_active: New active status being set
        :type new_active: bool
        :return: bool
        """
        is_changing_roles = user.role == 'admin' and new_role != 'admin'
        is_changing_active = user.active is True and new_active is None

        if is_changing_roles or is_changing_active:
            admin_count = User.query.filter(User.role == 'admin').count()
            active_count = User.query.filter(User.is_active is True).count()

            if admin_count == 1 or active_count == 1:
                return True

        return False

    def update_activity_tracking(self, ip_address):
        """
        Update various fields on the user that's related to meta data on their
        account, such as the sign in count and ip address, etc..

        :param ip_address: IP address
        :type ip_address: str
        :return: SQLAlchemy commit results
        """
        self.sign_in_count += 1

        self.last_sign_in_on = self.current_sign_in_on
        self.last_sign_in_ip = self.current_sign_in_ip

        #self.current_sign_in_on = datetime.datetime.now(pytz.utc)
        self.current_sign_in_ip = ip_address

        return self.save()

    @login.user_loader
    def load_user(id):
        return User.query.get(int(id))

    @classmethod
    def initialize_password_reset(cls, identity=None):
        """

        :param identity:
        :return:
        """

        u = User.find_by_identity(identity)
        reset_token = u.get_password_reset_token()

        from app.lib.flask_mail import (send_password_reset_mail)
        send_password_reset_mail(u, reset_token)

        return u
Example #24
0
class User(UserMixin, ResourceMixin, db.Model):
    ROLE = OrderedDict([('member', 'Member'), ('admin', 'Admin')])

    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)

    # Authentication.
    role = db.Column(db.Enum(*ROLE, name='role_types', native_enum=False),
                     index=True,
                     nullable=False,
                     server_default='member')
    active = db.Column('is_active',
                       db.Boolean(),
                       nullable=False,
                       server_default='1')
    username = db.Column(db.String(24), unique=True, index=True)
    email = db.Column(db.String(255),
                      unique=True,
                      index=True,
                      nullable=False,
                      server_default='')
    password = db.Column(db.String(128), nullable=False, server_default='')

    # Activity tracking.
    sign_in_count = db.Column(db.Integer, nullable=False, default=0)
    current_sign_in_on = db.Column(AwareDateTime())
    current_sign_in_ip = db.Column(db.String(45))
    last_sign_in_on = db.Column(AwareDateTime())
    last_sign_in_ip = db.Column(db.String(45))

    def __init__(self, **kwargs):
        # Call Flask-SQLAlchemy's constructor.
        super(User, self).__init__(**kwargs)

        self.password = User.encrypt_password(kwargs.get('password', ''))

    @classmethod
    def find_by_identity(cls, identity):
        """
        Find a user by their e-mail or username.

        :param identity: Email or username
        :type identity: str
        :return: User instance
        """
        return User.query.filter((User.email == identity)
                                 | (User.username == identity)).first()

    @classmethod
    def encrypt_password(cls, plaintext_password):
        """
        Hash a plaintext string using PBKDF2. This is good enough according
        to the NIST (National Institute of Standards and Technology).

        In other words while bcrypt might be superior in practice, if you use
        PBKDF2 properly (which we are), then your passwords are safe.

        :param plaintext_password: Password in plain text
        :type plaintext_password: str
        :return: str
        """
        if plaintext_password:
            return generate_password_hash(plaintext_password)

        return None

    @classmethod
    def deserialize_token(cls, token):
        """
        Obtain a user from de-serializing a signed token.

        :param token: Signed token.
        :type token: str
        :return: User instance or None
        """
        private_key = TimedJSONWebSignatureSerializer(
            current_app.config['SECRET_KEY'])
        try:
            decoded_payload = private_key.loads(token)

            return User.find_by_identity(decoded_payload.get('user_email'))
        except Exception:
            return None

    @classmethod
    def initialize_password_reset(cls, identity):
        """
        Generate a token to reset the password for a specific user.

        :param identity: User e-mail address or username
        :type identity: str
        :return: User instance
        """
        u = User.find_by_identity(identity)
        reset_token = u.serialize_token()

        # This prevents circular imports.
        from snakeeyes.blueprints.user.tasks import (
            deliver_password_reset_email)
        deliver_password_reset_email.delay(u.id, reset_token)

        return u

    def is_active(self):
        """
        Return whether or not the user account is active, this satisfies
        Flask-Login by overwriting the default value.

        :return: bool
        """
        return self.active

    def get_auth_token(self):
        """
        Return the user's auth token. Use their password as part of the token
        because if the user changes their password we will want to invalidate
        all of their logins across devices. It is completely fine to use
        md5 here as nothing leaks.

        This satisfies Flask-Login by providing a means to create a token.

        :return: str
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = URLSafeTimedSerializer(private_key)
        data = [str(self.id), md5(self.password.encode('utf-8')).hexdigest()]

        return serializer.dumps(data)

    def authenticated(self, with_password=True, password=''):
        """
        Ensure a user is authenticated, and optionally check their password.

        :param with_password: Optionally check their password
        :type with_password: bool
        :param password: Optionally verify this as their password
        :type password: str
        :return: bool
        """
        if with_password:
            return check_password_hash(self.password, password)

        return True

    def serialize_token(self, expiration=3600):
        """
        Sign and create a token that can be used for things such as resetting
        a password or other tasks that involve a one off token.

        :param expiration: Seconds until it expires, defaults to 1 hour
        :type expiration: int
        :return: JSON
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = TimedJSONWebSignatureSerializer(private_key, expiration)
        return serializer.dumps({'user_email': self.email}).decode('utf-8')

    def update_activity_tracking(self, ip_address):
        """
        Update various fields on the user that's related to meta data on their
        account, such as the sign in count and ip address, etc..

        :param ip_address: IP address
        :type ip_address: str
        :return: SQLAlchemy commit results
        """
        self.sign_in_count += 1

        self.last_sign_in_on = self.current_sign_in_on
        self.last_sign_in_ip = self.current_sign_in_ip

        self.current_sign_in_on = datetime.datetime.now(pytz.utc)
        self.current_sign_in_ip = ip_address

        return self.save()
Example #25
0
class OptionsContract(DefaultBase):
    equity_id = reference_col("equity")
    strike = Column(db.Float, nullable=False)
    exp = Column(db.Date, nullable=False)
    type = Column(db.Enum(OptionType), nullable=False)