Example #1
0
class OAuthGrant(db.Model):
    """
    A grant token is created in the authorization flow, 
    and will be destroyed when the authorization finished.
    """

    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('oauth2client.client_id'),
        nullable=False,
    )
    client = db.relationship('OAuthClient')

    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 []
Example #2
0
class OAuthClient(db.Model):
    """
    A client is the app which want to use the resource of a user. It is
    suggested that the client is registered by a user on your site, but it
    is not required.

    The client should contain at least these information:

        client_id: A random string
        client_secret: A random string
        client_type: A string represents if it is confidential
        redirect_uris: A list of redirect uris
        default_redirect_uri: One of the redirect uris
        default_scopes: Default scopes of the client

    But it could be better, if you implemented:

        allowed_grant_types: A list of grant types
        allowed_response_types: A list of response types
        validate_scopes: A function to validate scopes

    """

    __tablename__ = 'oauth2client'

    name = db.Column(
        db.String(40),
        info=dict(label='Name',
                  description='Name of application (displayed to users).',
                  validators=[validators.Required()]))
    """ Human readable name of the application. """

    description = db.Column(
        db.Text(),
        default=u'',
        info=dict(
            label='Description',
            description='Optional. Description of the application'
            ' (displayed to users).',
        ))
    """ Human readable description. """

    website = db.Column(
        URLType(),
        info=dict(
            label='Website URL',
            description='URL of your application (displayed to users).',
        ),
        default=u'',
    )

    user_id = db.Column(db.ForeignKey('users.id'))
    """ Creator of the client application. """

    client_id = db.Column(db.String(255), primary_key=True)
    """ Client application ID. """

    client_secret = db.Column(db.String(255),
                              unique=True,
                              index=True,
                              nullable=False)
    """ Client application secret. """

    is_confidential = db.Column(db.Boolean, default=True)
    """ Determine if client application is public or not.  """

    is_internal = db.Column(db.Boolean, default=False)
    """ Determins if client application is an internal application. """

    last_activity = db.Column(db.DateTime, nullable=True)
    """ Datetime that stores the last time this client was accessed. """

    _redirect_uris = db.Column(db.Text)
    """A newline-separated list of redirect URIs. First is the default URI."""

    _default_scopes = db.Column(db.Text)
    """A space-separated list of default scopes of the client.

    The value of the scope parameter is expressed as a list of space-delimited,
    case-sensitive strings.
    """

    user = db.relationship('User')
    """ Relationship to user. """
    @property
    def allowed_grant_types(self):
        return current_app.config['OAUTH2_ALLOWED_GRANT_TYPES']

    @property
    def allowed_response_types(self):
        return current_app.config['OAUTH2_ALLOWED_RESPONSE_TYPES']

    # def validate_scopes(self, scopes):
    #     return self._validate_scopes

    @property
    def client_type(self):
        if self.is_confidential:
            return 'confidential'
        return 'public'

    @property
    def redirect_uris(self):
        if self._redirect_uris:
            return self._redirect_uris.splitlines()
        return []

    @redirect_uris.setter
    def redirect_uris(self, value):
        """ Validate and store redirect URIs for client. """
        if isinstance(value, six.text_type):
            value = value.split("\n")

        value = [v.strip() for v in value]

        for v in value:
            self.validate_redirect_uri_form(v)

        self._redirect_uris = "\n".join(value) or ""

    @staticmethod
    def validate_redirect_uri_form(value):
        """ Validate a redirect URI.

        A redirect URL must utilize https or redirect to localhost.

        :param value: Value to validate.
        :raises: InvalidRedirectURIError, InsecureTransportError
        """
        sch, netloc, path, par, query, fra = urlparse(value)
        if not (sch and netloc):
            raise InvalidRedirectURIError()
        if sch != 'https':
            if ':' in netloc:
                netloc, port = netloc.split(':', 1)
            if not (netloc in ('localhost', '127.0.0.1') and sch == 'http'):
                raise InsecureTransportError()
        return True

    @property
    def default_redirect_uri(self):
        try:
            return self.redirect_uris[0]
        except IndexError:
            pass

    @property
    def default_scopes(self):
        """ List of default scopes for client. """
        if self._default_scopes:
            return self._default_scopes.split(" ")
        return []

    def validate_scopes(self, scopes):
        """ Validate if client is allowed to access scopes. """
        from .registry import scopes as scopes_registry

        for s in set(scopes):
            if s not in scopes_registry:
                return False
        return True

    def gen_salt(self):
        self.reset_client_id()
        self.reset_client_secret()

    def reset_client_id(self):
        self.client_id = gen_salt(
            current_app.config.get('OAUTH2_CLIENT_ID_SALT_LEN'))

    def reset_client_secret(self):
        self.client_secret = gen_salt(
            current_app.config.get('OAUTH2_CLIENT_SECRET_SALT_LEN'))
Example #3
0
class OAuthToken(db.Model):
    """
    A bearer token is the final token that can be used by the client.
    """
    __tablename__ = 'oauth2token'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    """ Object ID """

    client_id = db.Column(
        db.String(40),
        db.ForeignKey('oauth2client.client_id'),
        nullable=False,
    )
    """ Foreign key to client application """

    client = db.relationship('OAuthClient')
    """ SQLAlchemy relationship to client application """

    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.id', ondelete='CASCADE'))
    """ Foreign key to user """

    user = db.relationship('User')
    """ SQLAlchemy relationship to user """

    token_type = db.Column(db.String(255), default='bearer')
    """ Token type - only bearer is supported at the moment """

    access_token = db.Column(db.String(255), unique=True)

    refresh_token = db.Column(db.String(255), unique=True)

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

    _scopes = db.Column(db.Text)

    is_personal = db.Column(db.Boolean, default=False)
    """ Personal accesss token """

    is_internal = db.Column(db.Boolean, default=False)
    """ Determines if token is an internally generated token. """
    @property
    def scopes(self):
        if self._scopes:
            return self._scopes.split()
        return []

    @classmethod
    def create_personal(cls, name, user_id, scopes=None, is_internal=False):
        """
        Create a personal access token (a token that is bound to a specific
        user and which doesn't expire).
        """
        scopes = " ".join(scopes) if scopes else ""

        c = OAuthClient(name=name,
                        user_id=user_id,
                        is_internal=True,
                        is_confidential=False,
                        _default_scopes=scopes)
        c.gen_salt()

        t = OAuthToken(
            client_id=c.client_id,
            user_id=user_id,
            access_token=gen_salt(
                current_app.config.get('OAUTH2_TOKEN_PERSONAL_SALT_LEN', 40)),
            expires=None,
            _scopes=scopes,
            is_personal=True,
            is_internal=is_internal,
        )

        db.session.add(c)
        db.session.add(t)
        db.session.commit()

        return t