1
# Copyright 2012 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Supporting infrastructure for Piston-based APIs in MAAS."""
6
from __future__ import (
14
'AnonymousOperationsHandler',
19
from django.core.exceptions import PermissionDenied
20
from django.http import HttpResponseBadRequest
21
from piston.handler import (
26
from piston.resource import Resource
29
class OperationsResource(Resource):
30
"""A resource supporting operation dispatch.
32
All requests are passed onto the handler's `dispatch` method. See
33
:class:`OperationsHandler`.
36
crudmap = Resource.callmap
37
callmap = dict.fromkeys(crudmap, "dispatch")
40
class RestrictedResource(OperationsResource):
41
"""A resource that's restricted to active users."""
43
def authenticate(self, request, rm):
44
actor, anonymous = super(
45
RestrictedResource, self).authenticate(request, rm)
46
if not anonymous and not request.user.is_active:
47
raise PermissionDenied("User is not allowed access to this API.")
49
return actor, anonymous
52
class AdminRestrictedResource(RestrictedResource):
53
"""A resource that's restricted to administrators."""
55
def authenticate(self, request, rm):
56
actor, anonymous = super(
57
AdminRestrictedResource, self).authenticate(request, rm)
58
if anonymous or not request.user.is_superuser:
59
raise PermissionDenied("User is not allowed access to this API.")
61
return actor, anonymous
64
def operation(idempotent, exported_as=None):
65
"""Decorator to make a method available on the API.
67
:param idempotent: If this operation is idempotent. Idempotent operations
68
are made available via HTTP GET, non-idempotent operations via HTTP
70
:param exported_as: Optional operation name; defaults to the name of the
73
method = "GET" if idempotent else "POST"
76
if exported_as is None:
77
func.export = method, func.__name__
79
func.export = method, exported_as
85
class OperationsHandlerType(HandlerMetaClass):
86
"""Type for handlers that dispatch operations.
88
Collects all the exported operations, CRUD and custom, into the class's
89
`exports` attribute. This is a signature:function mapping, where signature
90
is an (http-method, operation-name) tuple. If operation-name is None, it's
93
The `allowed_methods` attribute is calculated as the union of all HTTP
94
methods required for the exported CRUD and custom operations.
97
def __new__(metaclass, name, bases, namespace):
98
cls = super(OperationsHandlerType, metaclass).__new__(
99
metaclass, name, bases, namespace)
101
# Create a signature:function mapping for CRUD operations.
103
(http_method, None): getattr(cls, method)
104
for http_method, method in OperationsResource.crudmap.items()
105
if getattr(cls, method, None) is not None
108
# Create a signature:function mapping for non-CRUD operations.
110
attribute.export: attribute
111
for attribute in vars(cls).values()
112
if getattr(attribute, "export", None) is not None
115
# Create the exports mapping.
118
exports.update(operations)
121
cls.exports = exports
122
cls.allowed_methods = frozenset(
123
http_method for http_method, name in exports)
128
class OperationsHandlerMixin:
129
"""Handler mixin for operations dispatch.
131
This enabled dispatch to custom functions that piggyback on HTTP methods
132
that ordinarily, in Piston, are used for CRUD operations.
134
This must be used in cooperation with :class:`OperationsResource` and
135
:class:`OperationsHandlerType`.
138
def dispatch(self, request, *args, **kwargs):
139
signature = request.method.upper(), request.REQUEST.get("op")
140
function = self.exports.get(signature)
142
return HttpResponseBadRequest(
143
"Unrecognised signature: %s %s" % signature)
145
return function(self, request, *args, **kwargs)
148
class OperationsHandler(
149
OperationsHandlerMixin, BaseHandler):
150
"""Base handler that supports operation dispatch."""
152
__metaclass__ = OperationsHandlerType
155
class AnonymousOperationsHandler(
156
OperationsHandlerMixin, AnonymousBaseHandler):
157
"""Anonymous base handler that supports operation dispatch."""
159
__metaclass__ = OperationsHandlerType