Added authenicators. Awesome.

This commit is contained in:
tom christie tom@tomchristie.com 2011-01-24 18:59:23 +00:00
parent e95198a1c0
commit eff54c00d5
3 changed files with 84 additions and 24 deletions

View File

@ -3,9 +3,15 @@ FlyWheel Documentation
This is the online documentation for FlyWheel - A REST framework for Django.
Some of FlyWheel's features:
* Clean, simple, class-based views for Resources.
* Easy input validation using Forms and ModelForms.
* Self describing APIs, with HTML and Plain Text outputs.
* Support for ModelResources with nice default implementations and input validation.
* Automatically provides a browse-able self-documenting API.
* Pluggable Emitters, Parsers and Authenticators - Easy to customise.
* Content type negotiation using Accept headers.
* Optional support for forms as input validation.
* Modular architecture - Easy to extend and modify.
.. toctree::
:maxdepth: 1

View File

@ -0,0 +1,44 @@
from django.contrib.auth import authenticate
import base64
class BaseAuthenticator(object):
"""All authenticators should extend BaseAuthenticator."""
def __init__(self, resource):
"""Initialise the authenticator with the Resource instance as state,
in case the authenticator needs to access any metadata on the Resource object."""
self.resource = resource
def authenticate(self, request):
"""Authenticate the request and return the authentication context or None.
The default permission checking on Resource will use the allowed_methods attribute
for permissions if the authentication context is not None, and use anon_allowed_methods otherwise.
The authentication context is passed to the method calls eg Resource.get(request, auth) in order to
allow them to apply any more fine grained permission checking at the point the response is being generated.
This function must be overridden to be implemented."""
return None
class BasicAuthenticator(BaseAuthenticator):
"""Use HTTP Basic authentication"""
def authenticate(self, request):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2 and auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
return user
return None
class UserLoggedInAuthenticator(BaseAuthenticator):
"""Use Djagno's built-in request session for authentication."""
def authenticate(self, request):
if request.user and request.user.is_active:
return request.user
return None

View File

@ -2,7 +2,7 @@ from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from flywheel import emitters, parsers
from flywheel import emitters, parsers, authenticators
from flywheel.response import status, Response, ResponseException
from decimal import Decimal
@ -49,6 +49,10 @@ class Resource(object):
parsers.XMLParser,
parsers.FormParser )
# List of all authenticating methods to attempt
authenticators = ( authenticators.UserLoggedInAuthenticator,
authenticators.BasicAuthenticator )
# Optional form for input validation and presentation of HTML formatted responses.
form = None
@ -81,7 +85,6 @@ class Resource(object):
""""""
# Setup the resource context
self.request = request
self.auth_context = None
self.response = None
self.form_instance = None
@ -123,7 +126,7 @@ class Resource(object):
# """Return an list of all the media types that this resource can emit."""
# return [parser.media_type for parser in self.parsers]
#def deafult_parser(self):
#def default_parser(self):
# return self.parsers[0]
@ -133,31 +136,22 @@ class Resource(object):
return self.add_domain(reverse(view, args=args, kwargs=kwargs))
def authenticate(self, request):
"""TODO"""
return None
# user = ...
# if DEBUG and request is from localhost
# if anon_user and not anon_allowed_methods raise PermissionDenied
# return auth_context
def get(self, request, *args, **kwargs):
def get(self, request, auth, *args, **kwargs):
"""Must be subclassed to be implemented."""
self.not_implemented('GET')
def post(self, request, content, *args, **kwargs):
def post(self, request, auth, content, *args, **kwargs):
"""Must be subclassed to be implemented."""
self.not_implemented('POST')
def put(self, request, content, *args, **kwargs):
def put(self, request, auth, content, *args, **kwargs):
"""Must be subclassed to be implemented."""
self.not_implemented('PUT')
def delete(self, request, *args, **kwargs):
def delete(self, request, auth, *args, **kwargs):
"""Must be subclassed to be implemented."""
self.not_implemented('DELETE')
@ -196,8 +190,24 @@ class Resource(object):
return method
def check_method_allowed(self, method):
def authenticate(self, request):
"""Attempt to authenticate the request, returning an authentication context or None"""
for authenticator in self.authenticators:
auth_context = authenticator(self).authenticate(request)
if auth_context:
return auth_context
return None
def check_method_allowed(self, method, auth):
"""Ensure the request method is acceptable for this resource."""
# If anonoymous check permissions and bail with no further info if disallowed
if auth is None and not method in self.anon_allowed_methods:
raise ResponseException(status.HTTP_403_FORBIDDEN,
{'detail': 'You do not have permission to access this resource. ' +
'You may need to login or otherwise authenticate the request.'})
if not method in self.callmap.keys():
raise ResponseException(status.HTTP_501_NOT_IMPLEMENTED,
{'detail': 'Unknown or unsupported method \'%s\'' % method})
@ -376,10 +386,10 @@ class Resource(object):
# Typically the context will be a user, or None if this is an anonymous request,
# but it could potentially be more complex (eg the context of a request key which
# has been signed against a particular set of permissions)
self.auth_context = self.authenticate(request)
auth_context = self.authenticate(request)
# Ensure the requested operation is permitted on this resource
self.check_method_allowed(method)
self.check_method_allowed(method, auth_context)
# Get the appropriate create/read/update/delete function
func = getattr(self, self.callmap.get(method, None))
@ -391,10 +401,10 @@ class Resource(object):
data = parser(self).parse(request.raw_post_data)
self.form_instance = self.get_form(data)
data = self.cleanup_request(data, self.form_instance)
response = func(request, data, *args, **kwargs)
response = func(request, auth_context, data, *args, **kwargs)
else:
response = func(request, *args, **kwargs)
response = func(request, auth_context, *args, **kwargs)
# Allow return value to be either Response, or an object, or None
if isinstance(response, Response):