Exemple #1
0
class UserExternalId(BaseMixin, db.Model):
    __tablename__ = 'userexternalid'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User, primaryjoin=user_id == User.id,
        backref=db.backref('externalids', cascade="all, delete-orphan"))
    service = db.Column(db.String(20), nullable=False)
    userid = db.Column(db.String(250), nullable=False)  # Unique id (or OpenID)
    username = db.Column(db.Unicode(80), nullable=True)
    oauth_token = db.Column(db.String(250), nullable=True)
    oauth_token_secret = db.Column(db.String(250), nullable=True)
    oauth_token_type = db.Column(db.String(250), nullable=True)

    __table_args__ = (db.UniqueConstraint("service", "userid"), {})
Exemple #2
0
class AuthCode(BaseMixin, db.Model):
    """Short-lived authorization tokens."""
    __tablename__ = 'authcode'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User, primaryjoin=user_id == User.id)
    client_id = db.Column(db.Integer,
                          db.ForeignKey('client.id'),
                          nullable=False)
    client = db.relationship(Client,
                             primaryjoin=client_id == Client.id,
                             backref=db.backref("authcodes",
                                                cascade="all, delete-orphan"))
    code = db.Column(db.String(44), default=newsecret, nullable=False)
    _scope = db.Column('scope', db.Unicode(250), nullable=False)
    redirect_uri = db.Column(db.Unicode(250), nullable=False)
    used = db.Column(db.Boolean, default=False, nullable=False)

    @property
    def scope(self):
        return self._scope.split(u' ')

    @scope.setter
    def scope(self, value):
        self._scope = u' '.join(value)

    scope = db.synonym('_scope', descriptor=scope)

    def add_scope(self, additional):
        if isinstance(additional, basestring):
            additional = [additional]
        self.scope = list(set(self.scope).union(set(additional)))
Exemple #3
0
class Team(BaseMixin, db.Model):
    __tablename__ = 'team'
    #: Unique and non-changing id
    userid = db.Column(db.String(22),
                       unique=True,
                       nullable=False,
                       default=newid)
    #: Displayed name
    title = db.Column(db.Unicode(250), nullable=False)
    #: Organization
    org_id = db.Column(db.Integer,
                       db.ForeignKey('organization.id'),
                       nullable=False)
    org = db.relationship(Organization,
                          primaryjoin=org_id == Organization.id,
                          backref=db.backref('teams',
                                             order_by=title,
                                             cascade='all, delete-orphan'))
    users = db.relationship(
        User, secondary='team_membership',
        backref='teams')  # No cascades here! Cascades will delete users

    def __repr__(self):
        return '<Team %s of %s>' % (self.title, self.org.title)

    @property
    def pickername(self):
        return self.title

    def permissions(self, user, inherited=None):
        perms = super(Team, self).permissions(user, inherited)
        if user and user in self.org.owners.users:
            perms.add('edit')
            perms.add('delete')
        return perms
Exemple #4
0
class UserEmail(BaseMixin, db.Model):
    __tablename__ = 'useremail'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User,
                           primaryjoin=user_id == User.id,
                           backref=db.backref('emails',
                                              cascade="all, delete-orphan"))
    _email = db.Column('email', db.Unicode(80), unique=True, nullable=False)
    md5sum = db.Column(db.String(32), unique=True, nullable=False)
    primary = db.Column(db.Boolean, nullable=False, default=False)

    def __init__(self, email, **kwargs):
        super(UserEmail, self).__init__(**kwargs)
        self._email = email
        self.md5sum = md5(self._email).hexdigest()

    @property
    def email(self):
        return self._email

    #: Make email immutable. There is no setter for email.
    email = db.synonym('_email', descriptor=email)

    def __repr__(self):
        return u'<UserEmail %s of user %s>' % (self.email, repr(self.user))

    def __unicode__(self):
        return unicode(self.email)

    def __str__(self):
        return str(self.__unicode__())
Exemple #5
0
class PasswordResetRequest(BaseMixin, db.Model):
    __tablename__ = 'passwordresetrequest'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User, primaryjoin=user_id == User.id)
    reset_code = db.Column(db.String(44), nullable=False, default=newsecret)

    def __init__(self, **kwargs):
        super(PasswordResetRequest, self).__init__(**kwargs)
        self.reset_code = newsecret()
Exemple #6
0
class SMSMessage(BaseMixin, db.Model):
    __tablename__ = 'smsmessage'
    # Phone number that the message was sent to
    phone_number = db.Column(db.String(15), nullable=False)
    transaction_id = db.Column(db.Unicode(40), unique=True, nullable=True)
    # The message itself
    message = db.Column(db.UnicodeText, nullable=False)
    # Flags
    status = db.Column(db.Integer, default=0, nullable=False)
    status_at = db.Column(db.DateTime, nullable=True)
    fail_reason = db.Column(db.Unicode(25), nullable=True)
Exemple #7
0
class UserEmailClaim(BaseMixin, db.Model):
    __tablename__ = 'useremailclaim'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship(User,
                           primaryjoin=user_id == User.id,
                           backref=db.backref('emailclaims',
                                              cascade="all, delete-orphan"))
    _email = db.Column('email', db.Unicode(80), nullable=True)
    verification_code = db.Column(db.String(44),
                                  nullable=False,
                                  default=newsecret)
    md5sum = db.Column(db.String(32), nullable=False)

    def __init__(self, email, **kwargs):
        super(UserEmailClaim, self).__init__(**kwargs)
        self.verification_code = newsecret()
        self._email = email
        self.md5sum = md5(self._email).hexdigest()

    @property
    def email(self):
        return self._email

    #: Make email immutable. There is no setter for email.
    email = db.synonym('_email', descriptor=email)

    def __repr__(self):
        return u'<UserEmailClaim %s of user %s>' % (self.email, repr(
            self.user))

    def __unicode__(self):
        return unicode(self.email)

    def __str__(self):
        return str(self.__unicode__())

    def permissions(self, user, inherited=None):
        perms = super(UserEmailClaim, self).permissions(user, inherited)
        if user and user == self.user:
            perms.add('verify')
        return perms
Exemple #8
0
class Client(BaseMixin, db.Model):
    """OAuth client applications"""
    __tablename__ = 'client'
    #: User who owns this client
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
    user = db.relationship(User,
                           primaryjoin=user_id == User.id,
                           backref=db.backref('clients',
                                              cascade="all, delete-orphan"))
    #: Organization that owns this client. Only one of this or user must be set
    org_id = db.Column(db.Integer,
                       db.ForeignKey('organization.id'),
                       nullable=True)
    org = db.relationship(Organization,
                          primaryjoin=org_id == Organization.id,
                          backref=db.backref('clients',
                                             cascade="all, delete-orphan"))
    #: Human-readable title
    title = db.Column(db.Unicode(250), nullable=False)
    #: Long description
    description = db.Column(db.UnicodeText, nullable=False, default=u'')
    #: Website
    website = db.Column(db.Unicode(250), nullable=False)
    #: Redirect URI
    redirect_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
    #: Back-end notification URI
    notification_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
    #: Front-end notification URI
    iframe_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
    #: Resource discovery URI
    resource_uri = db.Column(db.Unicode(250), nullable=True, default=u'')
    #: Active flag
    active = db.Column(db.Boolean, nullable=False, default=True)
    #: Allow anyone to login to this app?
    allow_any_login = db.Column(db.Boolean, nullable=False, default=True)
    #: Team access flag
    team_access = db.Column(db.Boolean, nullable=False, default=False)
    #: OAuth client key/id
    key = db.Column(db.String(22), nullable=False, unique=True, default=newid)
    #: OAuth client secret
    secret = db.Column(db.String(44), nullable=False, default=newsecret)
    #: Trusted flag: trusted clients are authorized to access user data
    #: without user consent, but the user must still login and identify themself.
    #: When a single provider provides multiple services, each can be declared
    #: as a trusted client to provide single sign-in across the services
    trusted = db.Column(db.Boolean, nullable=False, default=False)

    def secret_is(self, candidate):
        """
        Check if the provided client secret is valid.
        """
        return self.secret == candidate

    @property
    def owner(self):
        """
        Return human-readable owner name.
        """
        if self.user:
            return self.user.pickername
        elif self.org:
            return self.org.pickername
        else:
            raise AttributeError("This client has no owner")

    def owner_is(self, user):
        return self.user == user or (self.org and self.org
                                     in user.organizations_owned())

    def orgs_with_team_access(self):
        """
        Return a list of organizations that this client has access to the teams of.
        """
        return [
            cta.org for cta in self.org_team_access
            if cta.access_level == CLIENT_TEAM_ACCESS.ALL
        ]

    def permissions(self, user, inherited=None):
        perms = super(Client, self).permissions(user, inherited)
        perms.add('view')
        if user and self.owner_is(user):
            perms.add('edit')
            perms.add('delete')
            perms.add('assign-permissions')
            perms.add('new-resource')
        return perms
Exemple #9
0
class AuthToken(BaseMixin, db.Model):
    """Access tokens for access to data."""
    __tablename__ = 'authtoken'
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
                        nullable=True)  # Null for client-only tokens
    user = db.relationship(User, primaryjoin=user_id == User.id)
    client_id = db.Column(db.Integer,
                          db.ForeignKey('client.id'),
                          nullable=False)
    client = db.relationship(Client,
                             primaryjoin=client_id == Client.id,
                             backref=db.backref("authtokens",
                                                cascade="all, delete-orphan"))
    token = db.Column(db.String(22),
                      default=newid,
                      nullable=False,
                      unique=True)
    token_type = db.Column(db.String(250), default='bearer',
                           nullable=False)  # 'bearer', 'mac' or a URL
    secret = db.Column(db.String(44), nullable=True)
    _algorithm = db.Column('algorithm', db.String(20), nullable=True)
    _scope = db.Column('scope', db.Unicode(250), nullable=False)
    validity = db.Column(db.Integer, nullable=False,
                         default=0)  # Validity period in seconds
    refresh_token = db.Column(db.String(22), nullable=True, unique=True)

    # Only one authtoken per user and client. Add to scope as needed
    __table_args__ = (db.UniqueConstraint("user_id", "client_id"), {})

    def __init__(self, **kwargs):
        super(AuthToken, self).__init__(**kwargs)
        self.token = newid()
        if self.user:
            self.refresh_token = newid()
        self.secret = newsecret()

    def refresh(self):
        """
        Create a new token while retaining the refresh token.
        """
        if self.refresh_token is not None:
            self.token = newid()
            self.secret = newsecret()

    @property
    def scope(self):
        return self._scope.split(u' ')

    @scope.setter
    def scope(self, value):
        self._scope = u' '.join(value)

    scope = db.synonym('_scope', descriptor=scope)

    def add_scope(self, additional):
        if isinstance(additional, basestring):
            additional = [additional]
        self.scope = list(set(self.scope).union(set(additional)))

    @property
    def algorithm(self):
        return self._algorithm

    @algorithm.setter
    def algorithm(self, value):
        if value is None:
            self._algorithm = None
            self.secret = None
        elif value in ['hmac-sha-1', 'hmac-sha-256']:
            self._algorithm = value
        else:
            raise ValueError("Unrecognized algorithm '%s'" % value)

    algorithm = db.synonym('_algorithm', descriptor=algorithm)
Exemple #10
0
class Organization(BaseMixin, db.Model):
    __tablename__ = 'organization'
    # owners_id cannot be null, but must be declared with nullable=True since there is
    # a circular dependency. The post_update flag on the relationship tackles the circular
    # dependency within SQLAlchemy.
    owners_id = db.Column(db.Integer,
                          db.ForeignKey('team.id',
                                        use_alter=True,
                                        name='fk_organization_owners_id'),
                          nullable=True)
    owners = db.relationship('Team',
                             primaryjoin='Organization.owners_id == Team.id',
                             uselist=False,
                             cascade='all',
                             post_update=True)
    userid = db.Column(db.String(22),
                       unique=True,
                       nullable=False,
                       default=newid)
    _name = db.Column('name', db.Unicode(80), unique=True, nullable=True)
    title = db.Column(db.Unicode(80), default=u'', nullable=False)
    description = db.Column(db.UnicodeText, default=u'', nullable=False)

    def __init__(self, *args, **kwargs):
        super(Organization, self).__init__(*args, **kwargs)
        if self.owners is None:
            self.owners = Team(title=u"Owners", org=self)

    @hybrid_property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if self.valid_name(value):
            self._name = value

    def valid_name(self, value):
        existing = Organization.query.filter_by(name=value).first()
        if existing and existing.id != self.id:
            return False
        existing = User.query.filter_by(username=value).first()
        if existing:
            return False
        return True

    def __repr__(self):
        return '<Organization %s "%s">' % (self.name
                                           or self.userid, self.title)

    @property
    def pickername(self):
        if self.name:
            return '%s (~%s)' % (self.title, self.name)
        else:
            return self.title

    def clients_with_team_access(self):
        """
        Return a list of clients with access to the organization's teams.
        """
        from lastuserapp.models.client import CLIENT_TEAM_ACCESS
        return [
            cta.client for cta in self.client_team_access
            if cta.access_level == CLIENT_TEAM_ACCESS.ALL
        ]

    def permissions(self, user, inherited=None):
        perms = super(Organization, self).permissions(user, inherited)
        if user and user in self.owners.users:
            perms.add('view')
            perms.add('edit')
            perms.add('delete')
            perms.add('view-teams')
            perms.add('new-team')
        else:
            if 'view' in perms:
                perms.remove('view')
            if 'edit' in perms:
                perms.remove('edit')
            if 'delete' in perms:
                perms.remove('delete')
        return perms
Exemple #11
0
class User(BaseMixin, db.Model):
    __tablename__ = 'user'
    userid = db.Column(db.String(22),
                       unique=True,
                       nullable=False,
                       default=newid)
    fullname = db.Column(db.Unicode(80), default=u'', nullable=False)
    _username = db.Column('username',
                          db.Unicode(80),
                          unique=True,
                          nullable=True)
    pw_hash = db.Column(db.String(80), nullable=True)
    timezone = db.Column(db.Unicode(40), nullable=True)
    description = db.Column(db.UnicodeText, default=u'', nullable=False)

    def __init__(self, password=None, **kwargs):
        self.userid = newid()
        self.password = password
        super(User, self).__init__(**kwargs)

    def _set_password(self, password):
        if password is None:
            self.pw_hash = None
        else:
            self.pw_hash = bcrypt.hashpw(password, bcrypt.gensalt())

    password = property(fset=_set_password)

    @hybrid_property
    def username(self):
        return self._username

    @username.setter
    def username(self, value):
        if value is None:
            self._username = None
        elif self.valid_username(value):
            self._username = value

    def valid_username(self, value):
        existing = User.query.filter_by(username=value).first()
        if existing and existing.id != self.id:
            return False
        existing = Organization.query.filter_by(name=value).first()
        if existing:
            return False
        return True

    def password_is(self, password):
        if self.pw_hash is None:
            return False
        if self.pw_hash.startswith('sha1$'):
            return check_password_hash(self.pw_hash, password)
        else:
            return bcrypt.hashpw(password, self.pw_hash) == self.pw_hash

    def __repr__(self):
        return '<User %s "%s">' % (self.username or self.userid, self.fullname)

    def profileid(self):
        if self.username:
            return self.username
        else:
            return self.userid

    def displayname(self):
        return self.fullname or self.username or self.userid

    @property
    def pickername(self):
        if self.username:
            return '%s (~%s)' % (self.fullname, self.username)
        else:
            return self.fullname

    def add_email(self, email, primary=False):
        if primary:
            for emailob in self.emails:
                if emailob.primary:
                    emailob.primary = False
        useremail = UserEmail(user=self, email=email, primary=primary)
        db.session.add(useremail)
        return useremail

    def del_email(self, email):
        setprimary = False
        useremail = UserEmail.query.filter_by(user=self, email=email).first()
        if useremail:
            if useremail.primary:
                setprimary = True
            db.session.delete(useremail)
        if setprimary:
            for emailob in UserEmail.query.filter_by(user_id=self.id).all():
                if emailob is not useremail:
                    emailob.primary = True
                    break

    @cached_property
    def email(self):
        """
        Returns primary email address for user.
        """
        # Look for a primary address
        useremail = UserEmail.query.filter_by(user_id=self.id,
                                              primary=True).first()
        if useremail:
            return useremail
        # No primary? Maybe there's one that's not set as primary?
        useremail = UserEmail.query.filter_by(user_id=self.id).first()
        if useremail:
            # XXX: Mark at primary. This may or may not be saved depending on
            # whether the request ended in a database commit.
            useremail.primary = True
            return useremail
        # This user has no email address. Return a blank string instead of None
        # to support the common use case, where the caller will use unicode(user.email)
        # to get the email address as a string.
        return u''

    def organizations(self):
        """
        Return the organizations this user is a member of.
        """
        return list(set([team.org for team in self.teams]))

    def organizations_owned(self):
        """
        Return the organizations this user is an owner of.
        """
        return list(
            set([team.org for team in self.teams if team.org.owners == team]))

    def organizations_owned_ids(self):
        """
        Return the database ids of the organizations this user is an owner of. This is used
        for database queries.
        """
        return list(
            set([
                team.org.id for team in self.teams if team.org.owners == team
            ]))

    def is_profile_complete(self):
        """
        Return True if profile is complete (fullname, username and email are present), False
        otherwise.
        """
        return bool(self.fullname and self.username and self.email)

    @property
    def profile_url(self):
        return url_for('profile')