~lutostag/ubuntu/trusty/maas/1.5.4+keystone

« back to all changes in this revision

Viewing changes to src/maasserver/api_support.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2013-03-04 11:49:44 UTC
  • mto: This revision was merged to the branch mainline in revision 25.
  • Revision ID: package-import@ubuntu.com-20130304114944-azcvu9anlf8mizpa
Tags: upstream-1.3+bzr1452+dfsg
ImportĀ upstreamĀ versionĀ 1.3+bzr1452+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2012 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Supporting infrastructure for Piston-based APIs in MAAS."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
__metaclass__ = type
 
13
__all__ = [
 
14
    'AnonymousOperationsHandler',
 
15
    'operation',
 
16
    'OperationsHandler',
 
17
    ]
 
18
 
 
19
from django.core.exceptions import PermissionDenied
 
20
from django.http import HttpResponseBadRequest
 
21
from piston.handler import (
 
22
    AnonymousBaseHandler,
 
23
    BaseHandler,
 
24
    HandlerMetaClass,
 
25
    )
 
26
from piston.resource import Resource
 
27
 
 
28
 
 
29
class OperationsResource(Resource):
 
30
    """A resource supporting operation dispatch.
 
31
 
 
32
    All requests are passed onto the handler's `dispatch` method. See
 
33
    :class:`OperationsHandler`.
 
34
    """
 
35
 
 
36
    crudmap = Resource.callmap
 
37
    callmap = dict.fromkeys(crudmap, "dispatch")
 
38
 
 
39
 
 
40
class RestrictedResource(OperationsResource):
 
41
    """A resource that's restricted to active users."""
 
42
 
 
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.")
 
48
        else:
 
49
            return actor, anonymous
 
50
 
 
51
 
 
52
class AdminRestrictedResource(RestrictedResource):
 
53
    """A resource that's restricted to administrators."""
 
54
 
 
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.")
 
60
        else:
 
61
            return actor, anonymous
 
62
 
 
63
 
 
64
def operation(idempotent, exported_as=None):
 
65
    """Decorator to make a method available on the API.
 
66
 
 
67
    :param idempotent: If this operation is idempotent. Idempotent operations
 
68
        are made available via HTTP GET, non-idempotent operations via HTTP
 
69
        POST.
 
70
    :param exported_as: Optional operation name; defaults to the name of the
 
71
        exported method.
 
72
    """
 
73
    method = "GET" if idempotent else "POST"
 
74
 
 
75
    def _decorator(func):
 
76
        if exported_as is None:
 
77
            func.export = method, func.__name__
 
78
        else:
 
79
            func.export = method, exported_as
 
80
        return func
 
81
 
 
82
    return _decorator
 
83
 
 
84
 
 
85
class OperationsHandlerType(HandlerMetaClass):
 
86
    """Type for handlers that dispatch operations.
 
87
 
 
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
 
91
    a CRUD method.
 
92
 
 
93
    The `allowed_methods` attribute is calculated as the union of all HTTP
 
94
    methods required for the exported CRUD and custom operations.
 
95
    """
 
96
 
 
97
    def __new__(metaclass, name, bases, namespace):
 
98
        cls = super(OperationsHandlerType, metaclass).__new__(
 
99
            metaclass, name, bases, namespace)
 
100
 
 
101
        # Create a signature:function mapping for CRUD operations.
 
102
        crud = {
 
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
 
106
            }
 
107
 
 
108
        # Create a signature:function mapping for non-CRUD operations.
 
109
        operations = {
 
110
            attribute.export: attribute
 
111
            for attribute in vars(cls).values()
 
112
            if getattr(attribute, "export", None) is not None
 
113
            }
 
114
 
 
115
        # Create the exports mapping.
 
116
        exports = {}
 
117
        exports.update(crud)
 
118
        exports.update(operations)
 
119
 
 
120
        # Update the class.
 
121
        cls.exports = exports
 
122
        cls.allowed_methods = frozenset(
 
123
            http_method for http_method, name in exports)
 
124
 
 
125
        return cls
 
126
 
 
127
 
 
128
class OperationsHandlerMixin:
 
129
    """Handler mixin for operations dispatch.
 
130
 
 
131
    This enabled dispatch to custom functions that piggyback on HTTP methods
 
132
    that ordinarily, in Piston, are used for CRUD operations.
 
133
 
 
134
    This must be used in cooperation with :class:`OperationsResource` and
 
135
    :class:`OperationsHandlerType`.
 
136
    """
 
137
 
 
138
    def dispatch(self, request, *args, **kwargs):
 
139
        signature = request.method.upper(), request.REQUEST.get("op")
 
140
        function = self.exports.get(signature)
 
141
        if function is None:
 
142
            return HttpResponseBadRequest(
 
143
                "Unrecognised signature: %s %s" % signature)
 
144
        else:
 
145
            return function(self, request, *args, **kwargs)
 
146
 
 
147
 
 
148
class OperationsHandler(
 
149
    OperationsHandlerMixin, BaseHandler):
 
150
    """Base handler that supports operation dispatch."""
 
151
 
 
152
    __metaclass__ = OperationsHandlerType
 
153
 
 
154
 
 
155
class AnonymousOperationsHandler(
 
156
    OperationsHandlerMixin, AnonymousBaseHandler):
 
157
    """Anonymous base handler that supports operation dispatch."""
 
158
 
 
159
    __metaclass__ = OperationsHandlerType