class User(Model, UserMixin): id = db.Column(db.Integer(), primary_key=True) email = db.Column(db.String(), nullable=False) password = db.Column(db.String()) admin = db.Column(db.Boolean()) role = db.Column(db.String(), default='user') email_confirmed = db.Column(db.Boolean()) def __init__(self, email=None, password=None, admin=False): if not email: raise ValueError('No Email Provided') self.email = email.lower().strip() if admin: self.admin = True self.role = 'admin' # TODO: Clean this up if password: self.set_password(password) def set_password(self, password): self.password = generate_password_hash(password) def check_password(self, value): return check_password_hash(self.password, value) @property def is_authenticated(self): return not isinstance(self, AnonymousUserMixin) @property def is_admin(self): return self.role == 'admin' @property def is_anonymous(self): return isinstance(self, AnonymousUserMixin) def is_active(self): return True def get_id(self): return self.id def __repr__(self): return '<User {0}>'.format(self.email) @classmethod def lookup(cls, email): return cls.query.filter_by(email=email).first() @classmethod def lookup_or_create(cls, email, **kwargs): existing = cls.query.filter_by(email=email).first() if existing: return existing new_user = User(email=email, **kwargs) db.session.add(new_user) db.session.commit() return new_user
class TeamMember(Model): id = db.Column(db.Integer(), primary_key=True) team_id = db.Column(db.ForeignKey("team.id"), index=True, nullable=False) user_id = db.Column(db.ForeignKey("user.id"), index=True, nullable=True) invite_email = db.Column(db.String(255)) role = db.Column(db.String(), default='team_member') inviter_id = db.Column(db.ForeignKey("user.id")) invite_secret = db.Column(db.String(255), default=url_safe_token) activated = db.Column(db.Boolean(), default=False) team = db.relationship("Team", backref='members', lazy="joined") user = db.relationship("User", foreign_keys=[user_id], backref='memberships') inviter = db.relationship("User", foreign_keys=[inviter_id]) GDPR_EXPORT_COLUMNS = { "invite_email": "Email the invite was sent to", "activated": "Was the invite activated?", "created": "When the invite was created", "team_id": "What team was the invite for", "inviter_id": "Who sent the invite", "role": "Role on team" } @classmethod def invite(cls, team, email, role, inviter): invitee = User.lookup(email) if (not invitee): member = cls(team=team, invite_email=email, role=role, inviter=inviter) else: member = cls(team=team, user=invitee, role=role, inviter=inviter) db.session.add(member) db.session.commit() InviteEmail(member).send() @property def email(self): if self.user_id: return self.user.email return self.invite_email def activate(self, user_id): if not self.activated: self.user_id = user_id self.activated = True db.session.add(self) db.session.commit() self.team.billing_plan.record_change_in_usage() else: raise Exception('Already activated')
class Team(Model): """ A team is a collection of users sharing the same resources. All users get a team. Some teams have more than one member. Most resources in the application should belong to a team. Usage: team.members -> [<TeamMember>, ...] """ id = db.Column(db.Integer(), primary_key=True) creator_id = db.Column(db.ForeignKey("user.id"), index=True, nullable=False) name = db.Column(db.String(255)) # Plan may need to become DB backed when billing is introduced. plan = db.Column(db.String(), default='default') creator = db.relationship("User") GDPR_EXPORT_COLUMNS = { "name": "Name of the time", "plan": "What plan was", "created": "When the team was created" } def has_member(self, user): return user in [member.user for member in self.active_members] @property def active_members(self): return [ membership for membership in self.members if membership.activated ] @property def active_teams(self): return [ membership.team for membership in self.members if membership.activated ] @classmethod @transaction def create(cls, name, creator): new_team = cls(name=name, creator=creator) new_team_member = ModelProxy.teams.TeamMember(team=new_team, user=creator, role='administrator', activated=True) db.session.add(new_team) db.session.add(new_team_member) db.session.commit() return new_team
class TeamMember(Model): id = db.Column(db.Integer(), primary_key=True) team_id = db.Column(db.ForeignKey("team.id"), index=True, nullable=False) user_id = db.Column(db.ForeignKey("user.id"), index=True, nullable=True) invite_email = db.Column(db.String(255)) role = db.Column(db.String(), default='team_member') inviter_id = db.Column(db.ForeignKey("user.id")) invite_secret = db.Column(db.String(255), default=url_safe_token) activated = db.Column(db.Boolean(), default=False) team = db.relationship("Team", backref='members', lazy="joined") user = db.relationship("User", foreign_keys=[user_id], backref='memberships') inviter = db.relationship("User", foreign_keys=[inviter_id]) GDPR_EXPORT_COLUMNS = { "invite_email": "Email the invite was sent to", "activated": "Was the invite activated?", "created": "When the invite was created", "team_id": "What team was the invite for", "role": "Role on team" } @classmethod def invite(cls, team, email, role, inviter): invitee = User.lookup(email) if (not invitee): member = cls(team=team, invite_email=email, role=role, inviter=inviter) else: member = cls(team=team, user=invitee, role=role, inviter=inviter) db.session.add(member) db.session.commit() InviteEmail(member).send()
class TeamFile(Model): id = db.Column(db.Integer(), primary_key=True) team_id = db.Column(db.ForeignKey("team.id"), index=True, nullable=False) user_id = db.Column(db.ForeignKey("user.id"), index=True, nullable=True) file_name = db.Column(db.String()) description = db.Column(db.String()) file_object_name = db.Column(db.String()) activated = db.Column(db.Boolean(), default=False) team = db.relationship("Team", backref='files', lazy="joined") user = db.relationship("User", backref='team_files') GDPR_EXPORT_COLUMNS = { "created": "When the invite was created", "team_id": "What team was the invite for", "creator_id": "Who created the file", "file_name": "The name of the file", "description": "The description of the file", }
class User(Model, UserMixin): id = db.Column(db.Integer(), primary_key=True) full_name = db.Column(db.String()) email = db.Column(db.String(), nullable=False) password = db.Column(db.String()) admin = db.Column(db.Boolean()) role = db.Column(db.String(), default='user') email_confirmed = db.Column(db.Boolean()) user_api_key_hash = db.Column(db.String()) billing_customer_id = db.Column(db.String()) # Encrypted Secret (used for Two Factor Authentication) encrypted_totp_secret = db.Column( EncryptedType(db.String, key=global_encryption_key_iv, engine=FernetEngine)) GDPR_EXPORT_COLUMNS = { "id": "ID of the user", "hashid": "ID of User", "email": "User Email", "created": "When the user was created", "full_name": "The users full name", "email_confirmed": "Whether the email was confirmation" } def __init__(self, email=None, password=None, admin=False, email_confirmed=False, team=None, name=None): if not email: raise ValueError('No Email Provided') self.email = email.lower().strip() self.full_name = name self.email_confirmed = email_confirmed if admin: self.admin = True self.role = 'admin' # TODO: Clean this up if password: self.set_password(password) if not team: team_name = "{0}'s team".format(email) ModelProxy.teams.Team.create(team_name, self) def set_password(self, password): self.password = generate_password_hash(password) def check_password(self, value): return check_password_hash(self.password, value) def check_api_key_hash(self, api_key): # Potential timing attack here if self.user_api_key_hash: return check_password_hash(self.user_api_key_hash, api_key) return False def hash_api_key(self, api_key): self.user_api_key_hash = generate_password_hash(api_key) db.session.add(self) db.session.commit() @property def is_authenticated(self): return not isinstance(self, AnonymousUserMixin) @property def is_admin(self): return self.role == 'admin' @property def is_anonymous(self): return isinstance(self, AnonymousUserMixin) def is_active(self): return True def get_id(self): return self.id @property def active_memberships(self): return [member for member in self.memberships if member.activated] @property def admin_memberships(self): return [ member for member in self.memberships if member.activated and member.role == 'administrator' ] @property def active_teams(self): return [member.team for member in self.active_memberships] # TODO: Cache @property def primary_membership_id(self): if len(self.active_memberships) == 0: return None return self.active_memberships[0].id def __repr__(self): return '<User {0}>'.format(self.email) @classmethod def lookup(cls, email): return cls.query.filter_by(email=email).first() @classmethod def lookup_or_create_by_email(cls, email, **kwargs): existing = cls.query.filter_by(email=email).first() if existing: return existing new_user = User(email=email, **kwargs) db.session.add(new_user) db.session.commit() return new_user
class Team(Model): """ A team is a collection of users sharing the same resources. All users get a team. Some teams have more than one member. Most resources in the application should belong to a team. Usage: team.members -> [<TeamMember>, ...] """ id = db.Column(db.Integer(), primary_key=True) creator_id = db.Column(db.ForeignKey("user.id"), index=True, nullable=False) name = db.Column(db.String(255)) # Plan may need to become DB backed when billing is introduced. plan = db.Column(db.String(), default='free') plan_owner_id = db.Column(db.ForeignKey("user.id")) subscription_id = db.Column(db.String()) billing_customer_id = db.Column(db.String()) creator = db.relationship("User", foreign_keys=[creator_id]) plan_owner = db.relationship("User", foreign_keys=[plan_owner_id]) GDPR_EXPORT_COLUMNS = { "id": "ID of the team", "hashid": "ID of User", "name": "Name of the team", "plan": "What plan the team was on", "created": "When the team was created" } def has_member(self, user): return user in [member.user for member in self.active_members] @property def is_paid_plan(self): return not self.billing_plan.is_free @property def billing_plan(self): # TODO: Might benefit from a validation to ensure `plan` is a recognized type in billing_plans billing_plan = plans_by_name.get(self.plan) or FreePlan return billing_plan(self) @property def active_members(self): return [membership for membership in self.members if membership.activated] @property def active_teams(self): return [membership.team for membership in self.members if membership.activated] @classmethod @transaction def create(cls, name, creator): new_team = cls(name=name, creator=creator) new_team_member = ModelProxy.teams.TeamMember(team=new_team, user=creator, role='administrator', activated=True) db.session.add(new_team) db.session.add(new_team_member) db.session.commit() return new_team