summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIb Lundgren <ib.lundgren@gmail.com>2013-05-30 17:05:27 +0100
committerIb Lundgren <ib.lundgren@gmail.com>2013-05-30 17:05:27 +0100
commitba39888bf4d0224bc0bb9281f037402afbc46e12 (patch)
tree93e7ea76388c1b0f2c830959fa18061bb0a88935
parent99681d2b80619ba0cd17cccd08a6c63b9024421f (diff)
downloadoauthlib-ba39888bf4d0224bc0bb9281f037402afbc46e12.tar.gz
Remove framework specific decorators. Fix #167.
-rw-r--r--docs/decorators.rst21
-rw-r--r--docs/server2.rst98
-rw-r--r--oauthlib/oauth2/ext/__init__.py0
-rw-r--r--oauthlib/oauth2/ext/auditor.py108
-rw-r--r--oauthlib/oauth2/ext/django.py118
-rw-r--r--oauthlib/oauth2/ext/flask.py136
6 files changed, 25 insertions, 456 deletions
diff --git a/docs/decorators.rst b/docs/decorators.rst
deleted file mode 100644
index a005c85..0000000
--- a/docs/decorators.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-==========
-Decorators
-==========
-
-Hopefully, it should be quite straightforward to port the django decorator to other web frameworks as the decorator mainly provide a means for translating the framework specific request object into uri, http_method, headers and body.
-
-Django OAuth 2 Decorator
-------------------------
-
-TODO: hows and important notes about csrf, xss, etc
-
-Implementing a new OAuth 2 Provider Decorator
----------------------------------------------
-
-TODO: what needs to be done, what to keep an eye out for
-
-Extensions per web framework
-----------------------------
-
-If you are currently developing an extension, please let us know and we will
-list it here. Or better yet, send a PR!
diff --git a/docs/server2.rst b/docs/server2.rst
index 3b648e6..5c50d1a 100644
--- a/docs/server2.rst
+++ b/docs/server2.rst
@@ -2,35 +2,23 @@
OAuth 2: Creating a Provider
============================
-Note that OAuth 2 provider is still very much a work in progress, consider it a
-preview of a near future =)
+OAuthLib is a dependency free library that may be used with any web
+framework. That said, there are framework specific helper libraries
+to make your life easier.
-**1. Which framework are you using?**
+- For Django there is `django-oauth-toolkit`_.
+- For Flask there is `flask-oauthlib`_.
- OAuthLib is a dependency free library that may be used with any web
- framework. That said, there are framework specific helper decorator classes
- to make your life easier. The one we will be using in this example is for
- Django. For others, and information on how to create one, check out
- :doc:`decorators`.
+If there is no support for your favourite framework and you are interested
+in providing it then you have come to the right place. OAuthLib can handle
+the OAuth logic and leave you to support a few framework and setup specific
+tasks such as marshalling request objects into URI, headers and body arguments
+as well as provide an interface for a backend to store tokens, clients, etc.
- The main purpose of these decoraters is to help marshall between the
- framework specific request object and framework agnostic url, headers, body
- and http method parameters. They may also be useful for making sure common
- best security practices are followed.
+.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit
+.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib
- Their purpose is not to be a full solution to all your needs as a provider,
- for that you will want to seek out framework specific extensions building
- upon OAuthLib. See the section on :doc:`decorators` for a list of
- extensions.
-
- Relevant sections include:
-
- .. toctree::
- :maxdepth: 1
-
- decorators
-
-**2. Create your datastore models**
+**1. Create your datastore models**
These models will represent various OAuth specific concepts. There are a few
important links between them that the security of OAuth is based on. Below
@@ -191,7 +179,7 @@ preview of a near future =)
expires_at = django.db.models.DateTimeField()
-**3. Implement a validator**
+**2. Implement a validator**
The majority of the work involved in implementing an OAuth 2 provider
relates to mapping various validation and persistence methods to a storage
@@ -232,7 +220,7 @@ preview of a near future =)
security
-**4. Create your composite endpoint**
+**3. Create your composite endpoint**
Each of the endpoints can function independently from each other, however
for this example it is easier to consider them as one unit. An example of a
@@ -246,7 +234,6 @@ preview of a near future =)
validator = MyRequestValidator()
server = WebApplicationServer(validator)
- provider = OAuth2ProviderDecorator('/error', server) # See next section
Relevant sections include:
@@ -256,55 +243,20 @@ preview of a near future =)
preconfigured_servers
-**5. Decorate your endpoint views**
+**4. Create your endpoint views**
We are implementing support for the Authorization Code Grant and will
therefore need two views for the authorization, pre- and post-authorization
together with the token view. We also include an error page to redirect
users to if the client supplied invalid credentials in their redirection,
- for example an invalid redirect URI::
-
- @login_required
- @provider.pre_authorization_view
- def authorize(request, client_id=None, scopes=None, state=None,
- redirect_uri=None, response_type=None):
- # This is the traditional authorization page
- # Scopes will be the list of scopes client requested access too
- # You will want to present them in a nice form where the user can
- # select which scopes they allow the client to access.
- return render(request, 'authorize.html', {'scopes': scopes})
-
-
- @login_required
- @provider.post_authorization_view
- def authorization_response(request):
- # This is where the form submitted from authorize should end up
- # Which scopes user authorized access to + extra credentials you want
- # appended to the request object passed into the validator methods.
- # In almost every case, you will want to include the current
- # user in these extra credentials in order to associate the user with
- # the authorization code or bearer token.
- return request.POST.getlist['scopes'], {'user': request.user}
-
-
- @provider.access_token_view
- def token_response(request):
- # Not much too do here for you, return a dict with extra credentials
- # you want appended to request.credentials passed to the save_bearer_token
- # method of the validator.
- return {'extra': 'creds'}
-
- def error(request):
- # The /error page users will be redirected to if there was something
- # wrong with the credentials the client included when redirecting the
- # user to the authorization form. Mainly if the client was invalid or
- # included a malformed / invalid redirect url.
- # Error and description can be found in
- # GET['error'] and GET['error_description']
- return HttpResponse('Bad client! Warn user!')
-
-
-**6. Protect your APIs using scopes**
+ for example an invalid redirect URI.
+
+ The rest of this section will come soon.
+
+**5. Protect your APIs using scopes**
+
+ The definition of ``protected_resource_view`` will come soon. Please see
+ the framework specific extensions outlined in the beginning for now.
At this point you are ready to protect your API views with OAuth. Take some
time to come up with a good set of scopes as they can be very powerful in
@@ -330,7 +282,7 @@ preview of a near future =)
# A view that has its views functionally set.
return HttpResponse('pictures of cats')
-**7. Let us know how it went!**
+**6. Let us know how it went!**
Drop a line in our `G+ community`_ or open a `GitHub issue`_ =)
diff --git a/oauthlib/oauth2/ext/__init__.py b/oauthlib/oauth2/ext/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/oauthlib/oauth2/ext/__init__.py
+++ /dev/null
diff --git a/oauthlib/oauth2/ext/auditor.py b/oauthlib/oauth2/ext/auditor.py
deleted file mode 100644
index 8057d69..0000000
--- a/oauthlib/oauth2/ext/auditor.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# A functionality/security auditor that can be used to verify the correctness
-# of a live OAuth 2 provider implementation given certain credentials.
-
-# Implemented as a unittest class that you can subclass, configure and
-# run against any spec compliant provider.
-
-# Uses requests for HTTP requests, pip install requests
-# Uses selenium to drive authorizations, pip install selenium
-
-from __future__ import absolute_import
-
-import requests
-import selenium
-import unittest
-
-
-class OAuth1Auditor(unittest.TestCase):
- # Aiming for initial release in oauthlib 0.5
- pass
-
-
-class OAuth2Auditor(unittest.TestCase):
- """Verify your OAuth 2 provider implementation live."""
-
- def setUp(self):
- self.grant_types = ['supported', 'grant types']
- self.response_types = ['supported', 'response types']
- self.clients = [('client id', ['grant types'], ['redirect uris'], ['scope', 'list'])]
- self.authorization_url = 'https://your.site/authorize'
- self.token_url = 'https://your.site/token'
- self.protected_resources = [('/url', ['scope', 'list'])]
- self.configure()
-
- def configure(self):
- """Configure the credentials that will be exercised by the auditor."""
- raise NotImplementedError('Subclasses must implement this method.')
-
- def authenticate_client(self, request):
- """Add authentication credentials to the request object."""
- raise NotImplementedError('Subclasses must implement this method.')
-
- def select_scopes(self, webdriver, scopes):
- """Select scopes in your authorization view using webdriver."""
- raise NotImplementedError('Subclasses must implement this method.')
-
- def test_authorization_code_grant_flow(self):
- """Auth + Token + Refresh flow."""
- pass
-
- def test_implicit_grant_flow(self):
- """Auth + Token flow. Ensure no refresh token."""
- pass
-
- def test_password_grant_flow(self):
- """Token + Refresh flow."""
- pass
-
- def test_client_credentials_grant_flow(self):
- """Token flow. Ensure no refresh token."""
- pass
-
- def test_authorization_code_reuse(self):
- """Ensure authorization codes are only valid once."""
- pass
-
- def test_unauthorized_scope_access(self):
- """Ensure protected views account for scope restrictions."""
- pass
-
- def test_unauthorized_scope_request(self):
- """Ensure clients can't request scope access outside their reach."""
- pass
-
- def test_scope_change_during_authorization(self):
- """Ensure users can opt-out of scopes."""
- pass
-
- def test_mismatching_redirect_uri(self):
- """Ensure only exact matches of redirect URIs are allowed."""
- pass
-
- def test_no_default_redirect_uri(self):
- """Ensure no random redirect uri is set when no default exists."""
- pass
-
- def test_state_preservation(self):
- """Ensure state remains unchanged throughout auth flow."""
- pass
-
- def test_token_request_redirect_uri_mismatch(self):
- """Ensure redirect_uri is equal in auth and token request."""
- pass
-
- def test_https(self):
- """OAuth 2 **requires** HTTPS everywhere."""
- pass
-
- def test_client_grant_types(self):
- """Ensure client can only use one grant type."""
- pass
-
- def test_client_response_types(self):
- """Ensure client can only use the response type matching its grant type."""
- pass
-
- def test_wrong_client_valid_code(self):
- """Ensure one client can't use the auth code of another."""
- pass
diff --git a/oauthlib/oauth2/ext/django.py b/oauthlib/oauth2/ext/django.py
deleted file mode 100644
index 2a604b1..0000000
--- a/oauthlib/oauth2/ext/django.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden
-from django.views.decorators.csrf import csrf_exempt
-import functools
-import logging
-from oauthlib.common import urlencode
-from oauthlib.oauth2.draft25 import errors
-
-log = logging.getLogger('oauthlib')
-
-
-class OAuth2ProviderDecorator(object):
-
- def __init__(self, error_uri, server=None, authorization_endpoint=None,
- token_endpoint=None, resource_endpoint=None):
- self._authorization_endpoint = authorization_endpoint or server
- self._token_endpoint = token_endpoint or server
- self._resource_endpoint = resource_endpoint or server
- self._error_uri = error_uri
-
- def _extract_params(self, request):
- log.debug('Extracting parameters from request.')
- uri = request.build_absolute_uri()
- http_method = request.method
- headers = request.META
- if 'wsgi.input' in headers:
- del headers['wsgi.input']
- if 'wsgi.errors' in headers:
- del headers['wsgi.errors']
- if 'HTTP_AUTHORIZATION' in headers:
- headers['Authorization'] = headers['HTTP_AUTHORIZATION']
- body = urlencode(request.POST.items())
- return uri, http_method, body, headers
-
- def pre_authorization_view(self, f):
- @functools.wraps(f)
- def wrapper(request, *args, **kwargs):
- uri, http_method, body, headers = self._extract_params(request)
- redirect_uri = request.GET.get('redirect_uri', None)
- log.debug('Found redirect uri %s.', redirect_uri)
- try:
- scopes, credentials = self._authorization_endpoint.validate_authorization_request(
- uri, http_method, body, headers)
- log.debug('Saving credentials to session, %r.', credentials)
- request.session['oauth2_credentials'] = credentials
- kwargs['scopes'] = scopes
- kwargs.update(credentials)
- log.debug('Invoking view method, %r.', f)
- return f(request, *args, **kwargs)
-
- except errors.FatalClientError as e:
- log.debug('Fatal client error, redirecting to error page.')
- return HttpResponseRedirect(e.in_uri(self._error_uri))
- return wrapper
-
- def post_authorization_view(self, f):
- @functools.wraps(f)
- def wrapper(request, *args, **kwargs):
- uri, http_method, body, headers = self._extract_params(request)
- scopes, credentials = f(request, *args, **kwargs)
- log.debug('Fetched credentials view, %r.', credentials)
- credentials.update(request.session.get('oauth2_credentials', {}))
- log.debug('Fetched credentials from session, %r.', credentials)
- redirect_uri = credentials.get('redirect_uri')
- log.debug('Found redirect uri %s.', redirect_uri)
- try:
- url, headers, body, status = self._authorization_endpoint.create_authorization_response(
- uri, http_method, body, headers, scopes, credentials)
- log.debug('Authorization successful, redirecting to client.')
- return HttpResponseRedirect(url)
- except errors.FatalClientError as e:
- log.debug('Fatal client error, redirecting to error page.')
- return HttpResponseRedirect(e.in_uri(self._error_uri))
- except errors.OAuth2Error as e:
- log.debug('Client error, redirecting back to client.')
- return HttpResponseRedirect(e.in_uri(redirect_uri))
-
- return wrapper
-
- def access_token_view(self, f):
- @csrf_exempt
- @functools.wraps(f)
- def wrapper(request, *args, **kwargs):
- uri, http_method, body, headers = self._extract_params(request)
- credentials = f(request, *args, **kwargs)
- log.debug('Fetched credentials view, %r.', credentials)
- url, headers, body, status = self._token_endpoint.create_token_response(
- uri, http_method, body, headers, credentials)
- response = HttpResponse(content=body, status=status)
- for k, v in headers.items():
- response[k] = v
- return response
- return wrapper
-
- def protected_resource_view(self, scopes=None):
- def decorator(f):
- @csrf_exempt
- @functools.wraps(f)
- def wrapper(request, *args, **kwargs):
- try:
- scopes_list = scopes(request)
- except TypeError:
- scopes_list = scopes
- uri, http_method, body, headers = self._extract_params(request)
- valid, r = self._resource_endpoint.verify_request(
- uri, http_method, body, headers, scopes_list)
- kwargs.update({
- 'client': r.client,
- 'user': r.user,
- 'scopes': r.scopes
- })
- if valid:
- return f(request, *args, **kwargs)
- else:
- return HttpResponseForbidden()
- return wrapper
- return decorator
diff --git a/oauthlib/oauth2/ext/flask.py b/oauthlib/oauth2/ext/flask.py
deleted file mode 100644
index d6437a2..0000000
--- a/oauthlib/oauth2/ext/flask.py
+++ /dev/null
@@ -1,136 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals, absolute_import
-from flask import abort, make_response, redirect, request, session
-try:
- from flask import _app_ctx_stack as stack
-except ImportError:
- from flask import _request_ctx_stack as stack
-import functools
-import logging
-from oauthlib.common import urlencode
-from oauthlib.oauth2.draft25 import errors
-
-log = logging.getLogger('oauthlib')
-
-
-class OAuth2ProviderDecorator(object):
-
- def __init__(self, error_uri, server=None, authorization_endpoint=None,
- token_endpoint=None, resource_endpoint=None):
- self._authorization_endpoint = authorization_endpoint or server
- self._token_endpoint = token_endpoint or server
- self._resource_endpoint = resource_endpoint or server
- self._error_uri = error_uri
-
- def __getattr__(self, name):
- ctx = stack.top
- if ctx is not None:
- oauth_name = 'oauth_' + name
- try:
- return getattr(ctx, oauth_name)
- except AttributeError:
- return None
- return None
-
- def _set_ctx(self, name, value):
- ctx = stack.top
- if ctx is not None:
- oauth_name = 'oauth_' + name
- setattr(ctx, oauth_name, value)
-
- def _extract_params(self):
- log.debug('Extracting parameters from request.')
- uri = request.url
- http_method = request.method
- headers = dict(request.headers)
- if 'wsgi.input' in headers:
- del headers['wsgi.input']
- if 'wsgi.errors' in headers:
- del headers['wsgi.errors']
- if 'HTTP_AUTHORIZATION' in headers:
- headers['Authorization'] = headers['HTTP_AUTHORIZATION']
- body = urlencode(request.form.items())
- return uri, http_method, body, headers
-
- def pre_authorization_view(self, f):
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- uri, http_method, body, headers = self._extract_params()
- redirect_uri = request.args.get('redirect_uri', None)
- log.debug('Found redirect uri %s.', redirect_uri)
- try:
- scopes, credentials = self._authorization_endpoint.validate_authorization_request(
- uri, http_method, body, headers)
- log.debug('Saving credentials to session, %r.', credentials)
- session['oauth2_credentials'] = credentials
- kwargs['scopes'] = scopes
- kwargs.update(credentials)
- log.debug('Invoking view method, %r.', f)
- return f(*args, **kwargs)
-
- except errors.FatalClientError as e:
- log.debug('Fatal client error, redirecting to error page.')
- return redirect(e.in_uri(self._error_uri))
- return wrapper
-
- def post_authorization_view(self, f):
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- uri, http_method, body, headers = self._extract_params()
- scopes, credentials = f(*args, **kwargs)
- log.debug('Fetched credentials view, %r.', credentials)
- credentials.update(session.get('oauth2_credentials', {}))
- log.debug('Fetched credentials from session, %r.', credentials)
- redirect_uri = credentials.get('redirect_uri')
- log.debug('Found redirect uri %s.', redirect_uri)
- try:
- url, headers, body, status = self._authorization_endpoint.create_authorization_response(
- uri, http_method, body, headers, scopes, credentials)
- log.debug('Authorization successful, redirecting to client.')
- return redirect(url)
- except errors.FatalClientError as e:
- log.debug('Fatal client error, redirecting to error page.')
- return redirect(e.in_uri(self._error_uri))
- except errors.OAuth2Error as e:
- log.debug('Client error, redirecting back to client.')
- return redirect(e.in_uri(redirect_uri))
-
- return wrapper
-
- def access_token_view(self, f):
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- uri, http_method, body, headers = self._extract_params()
- credentials = f(*args, **kwargs)
- log.debug('Fetched credentials view, %r.', credentials)
- url, headers, body, status = self._token_endpoint.create_token_response(
- uri, http_method, body, headers, credentials)
- response = make_response(body, status)
- for k, v in headers.items():
- response.headers[k] = v
- return response
- return wrapper
-
- def protected_resource_view(self, scopes=None, forbidden=None):
- def decorator(f):
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- try:
- scopes_list = scopes()
- except TypeError:
- scopes_list = scopes
- uri, http_method, body, headers = self._extract_params()
- valid, r = self._resource_endpoint.verify_request(
- uri, http_method, body, headers, scopes_list)
- self._set_ctx('client', r.client)
- self._set_ctx('user', r.user)
- self._set_ctx('scopes', r.scopes)
- self._set_ctx('token_scopes', r.token_scopes)
- if valid:
- return f(*args, **kwargs)
- elif forbidden is not None:
- return forbidden()
- else:
- abort(401)
- return wrapper
- return decorator