Skip to content

Instantly share code, notes, and snippets.

@cwvh
Created July 18, 2009 08:22
Show Gist options
  • Save cwvh/149475 to your computer and use it in GitHub Desktop.
Save cwvh/149475 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
#
# Copyright 2009 Chris Van Horne
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.http import HttpResponse, HttpResponseRedirect
class Handler(object):
def __new__(cls, *args, **kwargs):
obj = super(Handler, cls).__new__(cls)
return obj(*args, **kwargs)
def __call__(self, request, *args, **kwargs):
try:
self.request = request
self.response = HttpResponse
method = request.method.lower()
return self.__getattribute__(method)(*args, **kwargs)
except AttributeError:
return self.response(status=NOT_IMPLEMENTED)
except Exception:
return self.response(status=INTERNAL_ERROR)
def get(self):
return self.response(status=NOT_IMPLEMENTED)
def post(self):
return self.response(status=NOT_IMPLEMENTED)
def put(self):
return self.response(status=NOT_IMPLEMENTED)
def delete(self):
return self.response(status=NOT_IMPLEMENTED)
def head(self):
return self.response(status=NOT_IMPLEMENTED)
def trace(self):
return self.response(status=NOT_IMPLEMENTED)
# HTTP Status Codes
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
#
# Informational - 1xx
CONTINUE = 100
SWITCHING_PROTOCOLS = 101
# Successful - 2xx
OK = 200
CREATED = 201
ACCEPTED = 202
NONAUTHORITATIVE_INFORMATION = 203
NO_CONTENT = 204
RESET_CONTENT = 205
PARTIAL_CONTENT = 206
# Redirection - 3xx
MULTIPLE_CHOICES = 300
MOVED_PERMANENTLY = 301
FOUND = 302
SEE_OTHER = 303
NOT_MODIFIED = 304
USE_PROXY = 305
UNUSED = 306
TEMPORARY_REDIRECT = 307
# Client Error - 4xx
BAD_REQUEST = 400
UNAUTHORIZED = 401
PAYMENT_REQUIRED = 402
FORBIDDEN = 403
NOT_FOUND = 404
METHOD_NOT_ALLOWED = 405
NOT_ACCEPTABLE = 406
PROXY_AUTH_REQUIRED = 407
REQUEST_TIMEOUT = 408
CONFLICT = 409
GONE = 410
LENGTH_REQUIRED = 411
PRECONDITION_FAILED = 412
REQUEST_TOO_LARGE = 413
URI_TOO_LONG = 414
UNSUPPORTED_MEDIA = 415
RANGE_NOT_SATISFIABLE = 416
EXPECTATION_FAILED = 417
# Server Error - 5xx
INTERNAL_ERROR = 500
NOT_IMPLEMENTED = 501
BAD_GATEWAY = 502
SERVICE_UNAVAILABLE = 503
GATEWAY_TIMEOUT = 504
VERSION_NOT_SUPPORTED = 505
#!/usr/bin/env python
#
# Copyright 2009 Chris Van Horne
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Defines a few utility decorators for restful.Handler instances."""
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
import base64
import restful
def auth_required(func):
"""Require request.user to be authenticated. This is currently
taylored to work only with restful.Handler, but could be corrected
to be general to any Django view.
Returns:
func or 403 Forbidden
"""
def _wrapper(self, *args, **kwargs):
if self.request.user.is_authenticated():
return func(self, *args, **kwargs)
else:
msg = "API requires site authentication."
return self.response(status=restful.FORBIDDEN, content=msg)
return _wrapper
# Decorator based on Scanner's original django snippet.
# See original here: http://www.djangosnippets.org/snippets/243/
def basic_http_auth_allowed(func):
"""This will attempt to fill self.request.user with a proper
user account if the client has attempted a basic HTTP auth.
Exceptional states are simplified since a failed auth will leave
self.request.user as None.
Returns:
func with self.request.user filled if good auth.
"""
def _wrapper(self, *args, **kwargs):
if 'HTTP_AUTHORIZATION' in self.request.META:
auth = self.request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == 'basic':
email, passwd = base64.b64decode(auth[1]).split(':')
uname = User.objects.get(email=email).username
user = authenticate(username=uname, password=passwd)
if user is not None:
if user.is_active:
login(self.request, user)
self.request.user = user
return func(self, *args, **kwargs)
return _wrapper
def basic_http_auth_required(func, realm=''):
"""Strict version basic_http_auth_allowable. The client must
successfully authenticate otherwise 401 "Unauthorized".
Returns:
func on success, 401 "Unauthorized" on failure.
"""
def _wrapper(self, *args, **kwargs):
if 'HTTP_AUTHORIZATION' in self.request.META:
auth = self.request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == 'basic':
email, passwd = base64.b64decode(auth[1]).split(':')
uname = User.objects.get(email=email).username
user = authenticate(username=uname, password=passwd)
if user is not None:
if user.is_active:
login(self.request, user)
self.request.user = user
return func(self, *args, **kwargs)
response = self.response(status=restful.UNAUTHORIZED)
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
return _wrapper
...
url(r'^favorites/$', 'Favorites', name='favorites'),
url(r'^favorites/(?P<id>\d+)/$', 'Favorites', name='favorites'),
...
import restful
from restful.decorators import auth_required, basic_http_auth_allowed
class Favorites(restful.Handler):
"""Favorites REST handler."""
@basic_http_auth_allowed
@auth_required
def get(self, id=None):
try:
user = self.request.user
if id is not None:
# This needs to be iterable for the comprehension below.
# Instead of building up a Q object, use ".get" so that
# multiple returns will raise an appropriate exception
# and we can return the correct HTTP status code.
favorite = (Favorite.objects.get(property__id=id, user=user),)
else:
favorite = Favorite.objects.filter(user=user)
output = json.write([x.as_dict() for x in favorite])
return self.response(status=200, content=output)
except Favorite.MultipleObjectsReturned, e:
return self.response(status=restful.CONFLICT, content=e)
except Favorite.DoesNotExist:
return self.response(status=restful.GONE)
@basic_http_auth_allowed
@auth_required
def put(self, id):
try:
property = Property.objects.get(id=id)
favorite, create = Favorite.objects.get_or_create(
user=self.request.user, property=property)
if create:
status = restful.CREATED
else:
status = restful.FOUND
r = self.response(status=status)
url = favorite.get_absolute_url()
r['Location'] = self.request.build_absolute_uri(url)
return r
except Property.DoesNotExist:
return self.response(status=restful.NOT_FOUND)
except Property.MultipleObjectsReturned, e:
return self.response(status=restful.CONFLICT, content=e)
@basic_http_auth_allowed
@auth_required
def delete(self, request, id):
try:
user = self.request.user
if not user.is_authenticated():
msg = "API requires site authentication."
return self.response(status=restful.FORBIDDEN, content=msg)
if id is not None:
Favorite.objects.get(property__id=id, user=user).delete()
else:
Favorite.objects.filter(user=user).delete()
return self.response(status=restful.NO_CONTENT)
except Favorite.MultipleObjectsReturned, e:
return self.response(status=restful.CONFLICT, content=e)
except Favorite.DoesNotExist:
return self.response(status=restful.GONE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment