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))
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)
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) - {''})
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
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})"
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, }
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
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)
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)
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)
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)"
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
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)
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))
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()
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
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)
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
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
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)
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
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()
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)