from flask import current_app from flask_login import AnonymousUserMixin, UserMixin, current_user from itsdangerous import BadSignature, SignatureExpired from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from werkzeug.security import check_password_hash, generate_password_hash from .. import db, login_manager # from flask_dance.consumer.backend.sqla import OAuthConsumerMixin, SQLAlchemyBackend # from .. import github_blueprint, google_blueprint, facebook_blueprint class Permission: GENERAL = 0x01 ADMINISTER = 0xff class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) index = db.Column(db.String(64)) default = db.Column(db.Boolean, default=False, index=True) permissions = db.Column(db.Integer) users = db.relationship('User', backref='role', lazy='dynamic') @staticmethod def insert_roles(): roles = { 'User': (Permission.GENERAL, 'main', True), 'Administrator': ( Permission.ADMINISTER, 'admin', False # grants all permissions ) } for r in roles: role = Role.query.filter_by(name=r).first() if role is None: role = Role(name=r) role.permissions = roles[r][0] role.index = roles[r][1] role.default = roles[r][2] db.session.add(role) db.session.commit() def __repr__(self): return '<Role \'%s\'>' % self.name class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) confirmed = db.Column(db.Boolean, default=False) first_name = db.Column(db.String(64), index=True) last_name = db.Column(db.String(64), index=True) email = db.Column(db.String(64), unique=True, index=True) password_hash = db.Column(db.String(128)) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) social_id = db.Column(db.String(64), unique=True) username = db.Column(db.String(64), unique=True) social_provider = db.Column(db.String(64)) def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: if self.email == current_app.config['ADMIN_EMAIL']: self.role = Role.query.filter_by( permissions=Permission.ADMINISTER).first() if self.role is None: self.role = Role.query.filter_by(default=True).first() def full_name(self): return '%s %s' % (self.first_name, self.last_name) def can(self, permissions): return self.role is not None and \ (self.role.permissions & permissions) == permissions def is_admin(self): return self.can(Permission.ADMINISTER) @property def password(self): raise AttributeError('`password` is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def generate_confirmation_token(self, expiration=604800): """Generate a confirmation token to email a new user.""" s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'confirm': self.id}) def generate_email_change_token(self, new_email, expiration=3600): """Generate an email change token to email an existing user.""" s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'change_email': self.id, 'new_email': new_email}) def generate_password_reset_token(self, expiration=3600): """ Generate a password reset change token to email to an existing user. """ s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'reset': self.id}) def confirm_account(self, token): """Verify that the provided token is for this user's id.""" s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except (BadSignature, SignatureExpired): return False if data.get('confirm') != self.id: return False self.confirmed = True db.session.add(self) db.session.commit() return True def change_email(self, token): """Verify the new email for this user.""" s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except (BadSignature, SignatureExpired): return False if data.get('change_email') != self.id: return False new_email = data.get('new_email') if new_email is None: return False if self.query.filter_by(email=new_email).first() is not None: return False self.email = new_email db.session.add(self) db.session.commit() return True def reset_password(self, token, new_password): """Verify the new password for this user.""" s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except (BadSignature, SignatureExpired): return False if data.get('reset') != self.id: return False self.password = new_password db.session.add(self) db.session.commit() return True @staticmethod def generate_fake(count=100, **kwargs): """Generate a number of fake users for testing.""" from sqlalchemy.exc import IntegrityError from random import seed, choice from faker import Faker fake = Faker() roles = Role.query.all() seed() for i in range(count): u = User( first_name=fake.first_name(), last_name=fake.last_name(), email=fake.email(), password='password', confirmed=True, role=choice(roles), **kwargs) db.session.add(u) try: db.session.commit() except IntegrityError: db.session.rollback() def __repr__(self): return '<User \'%s\'>' % self.full_name() class AnonymousUser(AnonymousUserMixin): def can(self, _): return False def is_admin(self): return False class Client(db.Model): client_id = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), nullable=False) user_id = db.Column(db.ForeignKey('users.id')) user = db.relationship('User') app_id = db.Column(db.ForeignKey('app.application_id')) app = db.relationship('App') _default_scopes = db.Column(db.Text) @property def allowed_grant_types(self): return ['client_credentials'] @property def client_type(self): return 'public' # @property # def redirect_uris(self): # if self._redirect_uris: # return self._redirect_uris.split() # return [] # # @property # def default_redirect_uri(self): # return self.redirect_uris[0] @property def default_scopes(self): if self._default_scopes: return self._default_scopes.split() return [] class Grant(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE')) user = db.relationship('User') client_id = db.Column( db.String(40), db.ForeignKey('client.client_id'), nullable=False, ) client = db.relationship('Client') code = db.Column(db.String(255), index=True, nullable=False) redirect_uri = db.Column(db.String(255)) expires = db.Column(db.DateTime) _scopes = db.Column(db.Text) def delete(self): db.session.delete(self) db.session.commit() return self @property def scopes(self): if self._scopes: return self._scopes.split() return [] class Token(db.Model): id = db.Column(db.Integer, primary_key=True) client_id = db.Column( db.String(40), db.ForeignKey('client.client_id'), nullable=False, ) client = db.relationship('Client') user_id = db.Column(db.Integer, db.ForeignKey('users.id')) user = db.relationship('User') # currently only bearer is supported token_type = db.Column(db.String(40)) access_token = db.Column(db.String(255), unique=True) expires = db.Column(db.DateTime) _scopes = db.Column(db.Text) @property def scopes(self): if self._scopes: return self._scopes.split() return [] class App(db.Model): application_id = db.Column(db.String(20), primary_key=True) application_name = db.Column(db.String(32), nullable=False) application_description = db.Column(db.String(200)) application_website = db.Column(db.String(128)) user_id = db.Column(db.ForeignKey('users.id')) user = db.relationship('User') # class OAuth(OAuthConsumerMixin, db.Model): # provider_user_id = db.Column(db.String(256), unique=True) # # user_id = db.Column(db.ForeignKey('users.id')) # user = db.relationship('User') class UserProgress(db.Model): user_progress_id = db.Column(db.Integer(), primary_key=True) chapter = db.Column(db.Integer(), nullable=False) verse = db.Column(db.String(3), nullable=False) timestamp = db.Column(db.TIMESTAMP()) user_id = db.Column(db.ForeignKey('users.id')) user = db.relationship('User') class UserFavourite(db.Model): user_favourite_id = db.Column(db.Integer(), primary_key=True) chapter = db.Column(db.Integer(), nullable=False) verse = db.Column(db.String(3), nullable=False) timestamp = db.Column(db.TIMESTAMP()) user_id = db.Column(db.ForeignKey('users.id')) user = db.relationship('User') # github_blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user) # google_blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user) # facebook_blueprint.backend = SQLAlchemyBackend(OAuth, db.session, user=current_user) login_manager.anonymous_user = AnonymousUser @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))