Skip to content

Commit

Permalink
Merge branch 'master' into thedrow-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
skion committed May 26, 2018
2 parents b7b850a + 3a1aa35 commit 4fe54e2
Show file tree
Hide file tree
Showing 16 changed files with 479 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -34,6 +34,7 @@ deploy:
on:
tags: true
all_branches: true
condition: $TOXENV = py36
repo: oauthlib/oauthlib
- provider: releases
api_key:
Expand All @@ -42,4 +43,5 @@ deploy:
on:
tags: true
all_branches: true
condition: $TOXENV = py36
repo: oauthlib/oauthlib
9 changes: 9 additions & 0 deletions CHANGELOG.rst
@@ -1,6 +1,15 @@
Changelog
=========

2.1.0 (2018-05-21)
------------------

* Fixed some copy and paste typos (#535)
* Use secrets module in Python 3.6 and later (#533)
* Add request argument to confirm_redirect_uri (#504)
* Avoid populating spurious token credentials (#542)
* Make populate attributes API public (#546)

2.0.7 (2018-03-19)
------------------

Expand Down
1 change: 1 addition & 0 deletions docs/feature_matrix.rst
Expand Up @@ -17,6 +17,7 @@ OAuth 2 client and provider support for
- Bearer Tokens
- Draft MAC tokens
- Token Revocation
- Token Introspection
- OpenID Connect Authentication

with support for SAML2 and JWT tokens, dynamic client registration and more to
Expand Down
6 changes: 5 additions & 1 deletion docs/oauth2/endpoints/endpoints.rst
Expand Up @@ -14,11 +14,12 @@ client attempts to access the user resources on their behalf.
:maxdepth: 2

authorization
introspect
token
resource
revocation

There are three different endpoints, the authorization endpoint which mainly
There are three main endpoints, the authorization endpoint which mainly
handles user authorization, the token endpoint which provides tokens and the
resource endpoint which provides access to protected resources. It is to the
endpoints you will feed requests and get back an almost complete response. This
Expand All @@ -27,3 +28,6 @@ later (but it's applicable to all other web frameworks librairies).

The main purpose of the endpoint in OAuthLib is to figure out which grant type
or token to dispatch the request to.

Then, you can extend your OAuth implementation by proposing introspect or
revocation endpoints.
26 changes: 26 additions & 0 deletions docs/oauth2/endpoints/introspect.rst
@@ -0,0 +1,26 @@
===================
Token introspection
===================

Introspect endpoints read opaque access and/or refresh tokens upon client
request. Also known as tokeninfo.

.. code-block:: python
# Initial setup
from your_validator import your_validator
server = WebApplicationServer(your_validator)
# Token revocation
uri = 'https://example.com/introspect'
headers, body, http_method = {}, 'token=sldafh309sdf', 'POST'
headers, body, status = server.create_introspect_response(uri,
headers=headers, body=body, http_method=http_method)
from your_framework import http_response
http_response(body, status=status, headers=headers)
.. autoclass:: oauthlib.oauth2.IntrospectEndpoint
:members:
2 changes: 1 addition & 1 deletion oauthlib/__init__.py
Expand Up @@ -12,6 +12,6 @@
from logging import NullHandler

__author__ = 'The OAuthlib Community'
__version__ = '2.0.7'
__version__ = '2.1.0'

logging.getLogger('oauthlib').addHandler(NullHandler())
1 change: 1 addition & 0 deletions oauthlib/oauth2/__init__.py
Expand Up @@ -15,6 +15,7 @@
from .rfc6749.clients import BackendApplicationClient
from .rfc6749.clients import ServiceApplicationClient
from .rfc6749.endpoints import AuthorizationEndpoint
from .rfc6749.endpoints import IntrospectEndpoint
from .rfc6749.endpoints import TokenEndpoint
from .rfc6749.endpoints import ResourceEndpoint
from .rfc6749.endpoints import RevocationEndpoint
Expand Down
1 change: 1 addition & 0 deletions oauthlib/oauth2/rfc6749/clients/base.py
Expand Up @@ -143,6 +143,7 @@ def prepare_request_body(self, *args, **kwargs):

def parse_request_uri_response(self, *args, **kwargs):
"""Abstract method used to parse redirection responses."""
raise NotImplementedError("Must be implemented by inheriting classes.")

def add_token(self, uri, http_method='GET', body=None, headers=None,
token_placement=None, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions oauthlib/oauth2/rfc6749/endpoints/__init__.py
Expand Up @@ -9,6 +9,7 @@
from __future__ import absolute_import, unicode_literals

from .authorization import AuthorizationEndpoint
from .introspect import IntrospectEndpoint
from .token import TokenEndpoint
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
Expand Down
135 changes: 135 additions & 0 deletions oauthlib/oauth2/rfc6749/endpoints/introspect.py
@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
"""
oauthlib.oauth2.rfc6749.endpoint.introspect
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An implementation of the OAuth 2.0 `Token Introspection`.
.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
"""
from __future__ import absolute_import, unicode_literals

import json
import logging

from oauthlib.common import Request

from ..errors import (InvalidClientError, InvalidRequestError, OAuth2Error,
UnsupportedTokenTypeError)
from .base import BaseEndpoint, catch_errors_and_unavailability

log = logging.getLogger(__name__)


class IntrospectEndpoint(BaseEndpoint):

"""Introspect token endpoint.
This endpoint defines a method to query an OAuth 2.0 authorization
server to determine the active state of an OAuth 2.0 token and to
determine meta-information about this token. OAuth 2.0 deployments
can use this method to convey information about the authorization
context of the token from the authorization server to the protected
resource.
To prevent the values of access tokens from leaking into
server-side logs via query parameters, an authorization server
offering token introspection MAY disallow the use of HTTP GET on
the introspection endpoint and instead require the HTTP POST method
to be used at the introspection endpoint.
"""

valid_token_types = ('access_token', 'refresh_token')

def __init__(self, request_validator, supported_token_types=None):
BaseEndpoint.__init__(self)
self.request_validator = request_validator
self.supported_token_types = (
supported_token_types or self.valid_token_types)

@catch_errors_and_unavailability
def create_introspect_response(self, uri, http_method='POST', body=None,
headers=None):
"""Create introspect valid or invalid response
If the authorization server is unable to determine the state
of the token without additional information, it SHOULD return
an introspection response indicating the token is not active
as described in Section 2.2.
"""
request = Request(uri, http_method, body, headers)
try:
self.validate_introspect_request(request)
log.debug('Token introspect valid for %r.', request)
except OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
return {}, e.json, e.status_code

claims = self.request_validator.introspect_token(
request.token,
request.token_type_hint,
request
)
headers = {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
'Pragma': 'no-cache',
}
if claims is None:
return headers, json.dumps(dict(active=False)), 200
if "active" in claims:
claims.pop("active")
return headers, json.dumps(dict(active=True, **claims)), 200

def validate_introspect_request(self, request):
"""Ensure the request is valid.
The protected resource calls the introspection endpoint using
an HTTP POST request with parameters sent as
"application/x-www-form-urlencoded".
token REQUIRED. The string value of the token.
token_type_hint OPTIONAL.
A hint about the type of the token submitted for
introspection. The protected resource MAY pass this parameter to
help the authorization server optimize the token lookup. If the
server is unable to locate the token using the given hint, it MUST
extend its search across all of its supported token types. An
authorization server MAY ignore this parameter, particularly if it
is able to detect the token type automatically.
* access_token: An Access Token as defined in [`RFC6749`],
`section 1.4`_
* refresh_token: A Refresh Token as defined in [`RFC6749`],
`section 1.5`_
The introspection endpoint MAY accept other OPTIONAL
parameters to provide further context to the query. For
instance, an authorization server may desire to know the IP
address of the client accessing the protected resource to
determine if the correct client is likely to be presenting the
token. The definition of this or any other parameters are
outside the scope of this specification, to be defined by
service documentation or extensions to this specification.
.. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
.. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
.. _`RFC6749`: http://tools.ietf.org/html/rfc6749
"""
if not request.token:
raise InvalidRequestError(request=request,
description='Missing token parameter.')

if self.request_validator.client_authentication_required(request):
if not self.request_validator.authenticate_client(request):
log.debug('Client authentication failed, %r.', request)
raise InvalidClientError(request=request)
elif not self.request_validator.authenticate_client_id(request.client_id, request):
log.debug('Client authentication failed, %r.', request)
raise InvalidClientError(request=request)

if (request.token_type_hint and
request.token_type_hint in self.valid_token_types and
request.token_type_hint not in self.supported_token_types):
raise UnsupportedTokenTypeError(request=request)
28 changes: 18 additions & 10 deletions oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
Expand Up @@ -18,13 +18,14 @@
ResourceOwnerPasswordCredentialsGrant)
from ..tokens import BearerToken, JWTToken
from .authorization import AuthorizationEndpoint
from .introspect import IntrospectEndpoint
from .resource import ResourceEndpoint
from .revocation import RevocationEndpoint
from .token import TokenEndpoint


class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
RevocationEndpoint):
class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
ResourceEndpoint, RevocationEndpoint):

"""An all-in-one endpoint featuring all four major grant types."""

Expand Down Expand Up @@ -91,10 +92,11 @@ def __init__(self, request_validator, token_expires_in=None,
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer, 'JWT': jwt})
RevocationEndpoint.__init__(self, request_validator)
IntrospectEndpoint.__init__(self, request_validator)


class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
RevocationEndpoint):
class WebApplicationServer(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
ResourceEndpoint, RevocationEndpoint):

"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""

Expand Down Expand Up @@ -129,10 +131,11 @@ def __init__(self, request_validator, token_generator=None,
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator)
IntrospectEndpoint.__init__(self, request_validator)


class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint,
RevocationEndpoint):
class MobileApplicationServer(AuthorizationEndpoint, IntrospectEndpoint,
ResourceEndpoint, RevocationEndpoint):

"""An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""

Expand Down Expand Up @@ -162,10 +165,12 @@ def __init__(self, request_validator, token_generator=None,
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])
IntrospectEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])


class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint,
RevocationEndpoint):
class LegacyApplicationServer(TokenEndpoint, IntrospectEndpoint,
ResourceEndpoint, RevocationEndpoint):

"""An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""

Expand Down Expand Up @@ -198,10 +203,11 @@ def __init__(self, request_validator, token_generator=None,
ResourceEndpoint.__init__(self, default_token='Bearer',
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator)
IntrospectEndpoint.__init__(self, request_validator)


class BackendApplicationServer(TokenEndpoint, ResourceEndpoint,
RevocationEndpoint):
class BackendApplicationServer(TokenEndpoint, IntrospectEndpoint,
ResourceEndpoint, RevocationEndpoint):

"""An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""

Expand Down Expand Up @@ -231,3 +237,5 @@ def __init__(self, request_validator, token_generator=None,
token_types={'Bearer': bearer})
RevocationEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])
IntrospectEndpoint.__init__(self, request_validator,
supported_token_types=['access_token'])
2 changes: 1 addition & 1 deletion oauthlib/oauth2/rfc6749/errors.py
Expand Up @@ -267,7 +267,7 @@ class UnsupportedGrantTypeError(OAuth2Error):

class UnsupportedTokenTypeError(OAuth2Error):
"""
The authorization server does not support the revocation of the
The authorization server does not support the hint of the
presented token type. I.e. the client tried to revoke an access token
on a server not supporting this feature.
"""
Expand Down
40 changes: 40 additions & 0 deletions oauthlib/oauth2/rfc6749/request_validator.py
Expand Up @@ -166,6 +166,46 @@ def is_within_original_scope(self, request_scopes, refresh_token, request, *args
"""
return False

def introspect_token(self, token, token_type_hint, request, *args, **kwargs):
"""Introspect an access or refresh token.
Called once the introspect request is validated. This method should
verify the *token* and either return a dictionary with the list of
claims associated, or `None` in case the token is unknown.
Below the list of registered claims you should be interested in:
- scope : space-separated list of scopes
- client_id : client identifier
- username : human-readable identifier for the resource owner
- token_type : type of the token
- exp : integer timestamp indicating when this token will expire
- iat : integer timestamp indicating when this token was issued
- nbf : integer timestamp indicating when it can be "not-before" used
- sub : subject of the token - identifier of the resource owner
- aud : list of string identifiers representing the intended audience
- iss : string representing issuer of this token
- jti : string identifier for the token
Note that most of them are coming directly from JWT RFC. More details
can be found in `Introspect Claims`_ or `_JWT Claims`_.
The implementation can use *token_type_hint* to improve lookup
efficency, but must fallback to other types to be compliant with RFC.
The dict of claims is added to request.token after this method.
:param token: The token string.
:param token_type_hint: access_token or refresh_token.
:param request: The HTTP Request (oauthlib.common.Request)
Method is used by:
- Introspect Endpoint (all grants are compatible)
.. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2
.. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4
"""
raise NotImplementedError('Subclasses must implement this method.')

def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
"""Invalidate an authorization code after use.
Expand Down

0 comments on commit 4fe54e2

Please sign in to comment.