← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/maas-add-node-piston-auth into lp:maas

 

Raphaël Badin has proposed merging lp:~rvb/maas/maas-add-node-piston-auth into lp:maas.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/maas-add-node-piston-auth/+merge/91034

This branch adds a custom piston authentication backend that acts exactly like piston's HttpBasicAuthentication except for the fact that (Django's) logged-in users are automatically authenticated for the API.

Drive-by fixes:
- add a few assertions to test the content type of API responses.
- reformat tests' comments.
-- 
https://code.launchpad.net/~rvb/maas/maas-add-node-piston-auth/+merge/91034
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/maas-add-node-piston-auth into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py	2012-01-31 12:15:08 +0000
+++ src/maasserver/api.py	2012-02-01 09:47:56 +0000
@@ -36,11 +36,26 @@
     MACAddress,
     Node,
     )
+from piston.authentication import HttpBasicAuthentication
 from piston.doc import generate_doc
 from piston.handler import BaseHandler
 from piston.utils import rc
 
 
+class DjangoHttpBasicAuthentication(HttpBasicAuthentication):
+    """A piston authentication class that uses the currently logged-in user
+    if there is one, and defaults to piston's HttpBasicAuthentication if not.
+
+    """
+
+    def is_authenticated(self, request):
+        if request.user.is_authenticated():
+            return request.user
+        else:
+            return super(
+                DjangoHttpBasicAuthentication, self).is_authenticated(request)
+
+
 def validate_and_save(obj):
     try:
         obj.full_clean()

=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py	2012-01-31 18:23:35 +0000
+++ src/maasserver/tests/test_api.py	2012-02-01 09:47:56 +0000
@@ -21,20 +21,23 @@
     Node,
     NODE_STATUS,
     )
-from maasserver.testing import TestCase
+from maasserver.testing import (
+    LoggedInTestCase,
+    TestCase,
+    )
 from maasserver.testing.factory import factory
 
 
 class NodeAnonAPITest(TestCase):
 
     def test_anon_nodes_GET(self):
-        """Anonymous requests to the API are denied."""
+        # Anonymous requests to the API are denied.
         response = self.client.get('/api/nodes/')
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_anon_api_doc(self):
-        """The documentation is accessible to anon users."""
+        # The documentation is accessible to anon users.
         response = self.client.get('/api/doc/')
 
         self.assertEqual(httplib.OK, response.status_code)
@@ -87,13 +90,23 @@
             status=NODE_STATUS.DEPLOYED, owner=self.other_user)
 
 
+class NodeAPILoggedInTest(LoggedInTestCase):
+
+    def test_nodes_GET_logged_in(self):
+        # A (Django) logged-in user can access the API.
+        node = factory.make_node()
+        response = self.client.get('/api/nodes/')
+        parsed_result = json.loads(response.content)
+
+        self.assertEqual(httplib.OK, response.status_code)
+        self.assertEqual(1, len(parsed_result))
+        self.assertEqual(node.system_id, parsed_result[0]['system_id'])
+
+
 class NodeAPITest(APITestMixin):
 
     def test_nodes_GET(self):
-        """
-        The api allows for fetching the list of Nodes.
-
-        """
+        # The api allows for fetching the list of Nodes.
         node1 = factory.make_node()
         node2 = factory.make_node(
             set_hostname=True, status=NODE_STATUS.DEPLOYED,
@@ -107,10 +120,7 @@
         self.assertEqual(node2.system_id, parsed_result[1]['system_id'])
 
     def test_node_GET(self):
-        """
-        The api allows for fetching a single Node (using system_id).
-
-        """
+        # The api allows for fetching a single Node (using system_id).
         node = factory.make_node(set_hostname=True)
         response = self.client.get('/api/nodes/%s/' % node.system_id)
         parsed_result = json.loads(response.content)
@@ -120,31 +130,23 @@
         self.assertEqual(node.system_id, parsed_result['system_id'])
 
     def test_node_GET_non_visible_node(self):
-        """
-        The request to fetch a single node is denied if the node isn't visible
-        by the user.
-
-        """
+        # The request to fetch a single node is denied if the node isn't
+        # visible by the user.
         response = self.client.get(
             '/api/nodes/%s/' % self.other_node.system_id)
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_node_GET_not_found(self):
-        """
-        When fetching a Node, the api returns a 'Not Found' (404) error
-        if no node is found.
-
-        """
+        # When fetching a Node, the api returns a 'Not Found' (404) error
+        # if no node is found.
         response = self.client.get('/api/nodes/invalid-uuid/')
 
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
 
     def test_nodes_POST(self):
-        """
-        The API allows a Node to be created and associated with MAC Addresses.
-
-        """
+        # The API allows a Node to be created and associated with MAC
+        # Addresses.
         response = self.client.post(
                 '/api/nodes/',
                 {
@@ -156,6 +158,8 @@
         parsed_result = json.loads(response.content)
 
         self.assertEqual(httplib.OK, response.status_code)
+        self.assertEqual(
+            'application/json; charset=utf-8', response['Content-Type'])
         self.assertEqual('diane', parsed_result['hostname'])
         self.assertEqual(41, len(parsed_result.get('system_id')))
         self.assertEqual(1, Node.objects.filter(hostname='diane').count())
@@ -166,11 +170,8 @@
             [mac.mac_address for mac in node.macaddress_set.all()])
 
     def test_nodes_POST_no_operation(self):
-        """
-        If there is no operation ('op=operation_name') specified in the
-        request data, a 'Bad request' response is returned.
-
-        """
+        # If there is no operation ('op=operation_name') specified in the
+        # request data, a 'Bad request' response is returned.
         response = self.client.post(
                 '/api/nodes/',
                 {
@@ -179,14 +180,13 @@
                 })
 
         self.assertEqual(httplib.BAD_REQUEST, response.status_code)
+        self.assertEqual(
+            'text/html; charset=utf-8', response['Content-Type'])
         self.assertEqual('Unknown operation.', response.content)
 
     def test_nodes_POST_bad_operation(self):
-        """
-        If the operation ('op=operation_name') specified in the
-        request data is unknown, a 'Bad request' response is returned.
-
-        """
+        # If the operation ('op=operation_name') specified in the
+        # request data is unknown, a 'Bad request' response is returned.
         response = self.client.post(
                 '/api/nodes/',
                 {
@@ -200,11 +200,8 @@
             "Unknown operation: 'invalid_operation'.", response.content)
 
     def test_nodes_POST_invalid(self):
-        """
-        If the data provided to create a node with MAC Addresse is invalid,
-        a 'Bad request' response is returned.
-
-        """
+        # If the data provided to create a node with MAC Addresse is invalid,
+        # a 'Bad request' response is returned.
         response = self.client.post(
                 '/api/nodes/',
                 {
@@ -215,16 +212,15 @@
         parsed_result = json.loads(response.content)
 
         self.assertEqual(httplib.BAD_REQUEST, response.status_code)
+        self.assertEqual(
+            'application/json; charset=utf-8', response['Content-Type'])
         self.assertEqual(['mac_addresses'], list(parsed_result))
         self.assertEqual(
             ["One or more MAC Addresses is invalid."],
             parsed_result['mac_addresses'])
 
     def test_node_PUT(self):
-        """
-        The api allows to update a Node.
-
-        """
+        # The api allows to update a Node.
         node = factory.make_node(hostname='diane')
         response = self.client.put(
             '/api/nodes/%s/' % node.system_id, {'hostname': 'francis'})
@@ -236,11 +232,8 @@
         self.assertEqual(1, Node.objects.filter(hostname='francis').count())
 
     def test_node_resource_uri(self):
-        """
-        When a Node is returned by the API, the field 'resource_uri' provides
-        the URI for this Node.
-
-        """
+        # When a Node is returned by the API, the field 'resource_uri'
+        # provides the URI for this Node.
         node = factory.make_node(hostname='diane')
         response = self.client.put(
             '/api/nodes/%s/' % node.system_id, {'hostname': 'francis'})
@@ -251,11 +244,8 @@
             parsed_result['resource_uri'])
 
     def test_node_PUT_invalid(self):
-        """
-        If the data provided to update a node is invalid, a 'Bad request'
-        response is returned.
-
-        """
+        # If the data provided to update a node is invalid, a 'Bad request'
+        # response is returned.
         node = factory.make_node(hostname='diane')
         response = self.client.put(
             '/api/nodes/%s/' % node.system_id,
@@ -264,42 +254,30 @@
         self.assertEqual(httplib.BAD_REQUEST, response.status_code)
 
     def test_node_PUT_non_visible_node(self):
-        """
-        The request to update a single node is denied if the node isn't visible
-        by the user.
-
-        """
+        # The request to update a single node is denied if the node isn't
+        # visible by the user.
         response = self.client.put(
             '/api/nodes/%s/' % self.other_node.system_id)
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_node_PUT_not_found(self):
-        """
-        When updating a Node, the api returns a 'Not Found' (404) error
-        if no node is found.
-
-        """
+        # When updating a Node, the api returns a 'Not Found' (404) error
+        # if no node is found.
         response = self.client.put('/api/nodes/no-node-here/')
 
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
 
     def test_node_DELETE_non_visible_node(self):
-        """
-        The request to delete a single node is denied if the node isn't visible
-        by the user.
-
-        """
+        # The request to delete a single node is denied if the node isn't
+        # visible by the user.
         response = self.client.delete(
             '/api/nodes/%s/' % self.other_node.system_id)
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_node_DELETE(self):
-        """
-        The api allows to delete a Node.
-
-        """
+        # The api allows to delete a Node.
         node = factory.make_node(set_hostname=True)
         system_id = node.system_id
         response = self.client.delete('/api/nodes/%s/' % node.system_id)
@@ -309,11 +287,8 @@
             [], list(Node.objects.filter(system_id=system_id)))
 
     def test_node_DELETE_not_found(self):
-        """
-        When deleting a Node, the api returns a 'Not Found' (404) error
-        if no node is found.
-
-        """
+        # When deleting a Node, the api returns a 'Not Found' (404) error
+        # if no node is found.
         response = self.client.delete('/api/nodes/no-node-here/')
 
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
@@ -328,10 +303,7 @@
         self.mac2 = self.node.add_mac_address('22:bb:cc:dd:aa:ff')
 
     def test_macs_GET(self):
-        """
-        The api allows for fetching the list of the MAC Addresss for a node.
-
-        """
+        # The api allows for fetching the list of the MAC Addresss for a node.
         response = self.client.get('/api/nodes/%s/macs/' % self.node.system_id)
         parsed_result = json.loads(response.content)
 
@@ -343,64 +315,46 @@
             self.mac2.mac_address, parsed_result[1]['mac_address'])
 
     def test_macs_GET_forbidden(self):
-        """
-        When fetching MAC Addresses, the api returns a 'Unauthorized' (401)
-        error if the node is not visible to the logged-in user.
-
-        """
+        # When fetching MAC Addresses, the api returns a 'Unauthorized' (401)
+        # error if the node is not visible to the logged-in user.
         response = self.client.get(
             '/api/nodes/%s/macs/' % self.other_node.system_id)
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_macs_GET_not_found(self):
-        """
-        When fetching MAC Addresses, the api returns a 'Not Found' (404)
-        error if no node is found.
-
-        """
+        # When fetching MAC Addresses, the api returns a 'Not Found' (404)
+        # error if no node is found.
         response = self.client.get('/api/nodes/invalid-id/macs/')
 
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
 
     def test_macs_GET_node_not_found(self):
-        """
-        When fetching a MAC Address, the api returns a 'Not Found' (404)
-        error if the MAC Address does not exist.
-
-        """
+        # When fetching a MAC Address, the api returns a 'Not Found' (404)
+        # error if the MAC Address does not exist.
         response = self.client.get(
             '/api/nodes/%s/macs/00-aa-22-cc-44-dd/' % self.node.system_id)
 
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
 
     def test_macs_GET_node_forbidden(self):
-        """
-        When fetching a MAC Address, the api returns a 'Unauthorized' (401)
-        error if the node is not visible to the logged-in user.
-
-        """
+        # When fetching a MAC Address, the api returns a 'Unauthorized' (401)
+        # error if the node is not visible to the logged-in user.
         response = self.client.get(
             '/api/nodes/%s/macs/0-aa-22-cc-44-dd/' % self.other_node.system_id)
 
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_macs_GET_node_bad_request(self):
-        """
-        When fetching a MAC Address, the api returns a 'Bad Request' (400)
-        error if the MAC Address is not valid.
-
-        """
+        # When fetching a MAC Address, the api returns a 'Bad Request' (400)
+        # error if the MAC Address is not valid.
         response = self.client.get(
             '/api/nodes/%s/macs/invalid-mac/' % self.node.system_id)
 
         self.assertEqual(400, response.status_code)
 
     def test_macs_POST_add_mac(self):
-        """
-        The api allows to add a MAC Address to an existing node.
-
-        """
+        # The api allows to add a MAC Address to an existing node.
         nb_macs = MACAddress.objects.filter(node=self.node).count()
         response = self.client.post(
             '/api/nodes/%s/macs/' % self.node.system_id,
@@ -414,11 +368,8 @@
             MACAddress.objects.filter(node=self.node).count())
 
     def test_macs_POST_add_mac_invalid(self):
-        """
-        A 'Bad Request' response is returned if one tries to add an invalid
-        MAC Address to a node.
-
-        """
+        # A 'Bad Request' response is returned if one tries to add an invalid
+        # MAC Address to a node.
         response = self.client.post(
             '/api/nodes/%s/macs/' % self.node.system_id,
             {'mac_address': 'invalid-mac'})
@@ -431,10 +382,7 @@
             parsed_result['mac_address'])
 
     def test_macs_DELETE_mac(self):
-        """
-        The api allows to delete a MAC Address.
-
-        """
+        # The api allows to delete a MAC Address.
         nb_macs = self.node.macaddress_set.count()
         response = self.client.delete(
             '/api/nodes/%s/macs/%s/' % (
@@ -446,11 +394,8 @@
             self.node.macaddress_set.count())
 
     def test_macs_DELETE_mac_forbidden(self):
-        """
-        When deleting a MAC Address, the api returns a 'Unauthorized' (401)
-        error if the node is not visible to the logged-in user.
-
-        """
+        # When deleting a MAC Address, the api returns a 'Unauthorized' (401)
+        # error if the node is not visible to the logged-in user.
         response = self.client.delete(
             '/api/nodes/%s/macs/%s/' % (
                 self.other_node.system_id, self.mac1.mac_address))
@@ -458,11 +403,8 @@
         self.assertEqual(httplib.UNAUTHORIZED, response.status_code)
 
     def test_macs_DELETE_not_found(self):
-        """
-        When deleting a MAC Address, the api returns a 'Not Found' (404)
-        error if no existing MAC Address is found.
-
-        """
+        # When deleting a MAC Address, the api returns a 'Not Found' (404)
+        # error if no existing MAC Address is found.
         response = self.client.delete(
             '/api/nodes/%s/macs/%s/' % (
                 self.node.system_id, '00-aa-22-cc-44-dd'))
@@ -470,11 +412,8 @@
         self.assertEqual(httplib.NOT_FOUND, response.status_code)
 
     def test_macs_DELETE_bad_request(self):
-        """
-        When deleting a MAC Address, the api returns a 'Bad Request' (400)
-        error if the provided MAC Address is not valid.
-
-        """
+        # When deleting a MAC Address, the api returns a 'Bad Request' (400)
+        # error if the provided MAC Address is not valid.
         response = self.client.delete(
             '/api/nodes/%s/macs/%s/' % (
                 self.node.system_id, 'invalid-mac'))

=== modified file 'src/maasserver/urls.py'
--- src/maasserver/urls.py	2012-01-31 18:23:35 +0000
+++ src/maasserver/urls.py	2012-02-01 09:47:56 +0000
@@ -22,6 +22,7 @@
     )
 from maasserver.api import (
     api_doc,
+    DjangoHttpBasicAuthentication,
     NodeHandler,
     NodeMacHandler,
     NodeMacsHandler,
@@ -33,7 +34,6 @@
     NodeListView,
     NodesCreateView,
     )
-from piston.authentication import HttpBasicAuthentication
 from piston.resource import Resource
 
 # Urls accessible to anonymous users.
@@ -61,7 +61,7 @@
 )
 
 # API
-auth = HttpBasicAuthentication(realm="MaaS API")
+auth = DjangoHttpBasicAuthentication(realm="MaaS API")
 
 node_handler = Resource(NodeHandler, authentication=auth)
 nodes_handler = Resource(NodesHandler, authentication=auth)