<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,4 +1,3 @@
-from django.contrib.auth.models import User
 from django.core.paginator import Paginator, InvalidPage
 from django.core import urlresolvers
 from django.http import Http404, HttpResponseForbidden, HttpResponseBadRequest
@@ -7,7 +6,8 @@ from piston.handler import BaseHandler
 from piston.utils import require_mime
 from tagging.models import Tag
 from .models import Project, Build, BuildStep
-from .utils import link, allow_404, format_dt, HttpResponseUnauthorized, HttpResponseCreated
+from .utils import (link, allow_404, authenticated, format_dt,
+                    HttpResponseCreated, HttpResponseNoContent)
 
 class ProjectListHandler(BaseHandler):
     allowed_methods = ['GET']
@@ -20,7 +20,7 @@ class ProjectListHandler(BaseHandler):
         }
 
 class ProjectHandler(BaseHandler):
-    allowed_methods = ['GET', 'PUT']
+    allowed_methods = ['GET', 'PUT', 'DELETE']
     model = Project
     viewname = 'project_detail'
     fields = ('name', 'owner', 'links')
@@ -29,6 +29,7 @@ class ProjectHandler(BaseHandler):
     def read(self, request, slug):
         return get_object_or_404(Project, slug=slug)
     
+    @authenticated
     @require_mime('json')
     def update(self, request, slug):
         # Check the one required field in the PUT data -- a name
@@ -36,31 +37,7 @@ class ProjectHandler(BaseHandler):
             project_name = request.data['name']
         except (TypeError, KeyError):
             return HttpResponseBadRequest()
-                    
-        # PUT isn't allowed if we're not authenticated
-        if 'HTTP_AUTHORIZATION' not in request.META:
-            return HttpResponseUnauthorized()
-                
-        # Get or create a user from the Authorization header.
-        try:
-            authtype, auth = request.META['HTTP_AUTHORIZATION'].split(' ')
-            if authtype.lower() != 'basic':
-                return HttpResponseUnauthorized()
-            username, password = auth.decode('base64').split(':')
-            user = User.objects.get(username=username)
-            new_user = False
-        except ValueError:
-            # Raised if split()/unpack fails
-            return HttpResponseUnauthorized()
-        except User.DoesNotExist:
-            user = User(username=username)
-            user.set_password(password)
-            new_user = True
-                    
-        # Make sure the password's correct.
-        if not user.check_password(password):
-            return HttpResponseForbidden()
-            
+        
         # If there's an existing project, the updating user has to match the
         # user who created the project
         try:
@@ -68,15 +45,15 @@ class ProjectHandler(BaseHandler):
         except Project.DoesNotExist:
             # The project doesn't exist, so save the user if it's a
             # new one, create the project, then return 201 created
-            if new_user:
-                user.save()
-            project = Project.objects.create(name=project_name, slug=slug, owner=user)
+            if request.user.is_new_user:
+                request.user.save()
+            project = Project.objects.create(name=project_name, slug=slug, owner=request.user)
             return HttpResponseCreated(urlresolvers.reverse(self.viewname, args=[slug]))
                 
         # Okay, so if we fall through to here then we're trying to update an
         # existing project. This means checking that the user's allowed to
         # do so before updating it.
-        if new_user or project.owner != user:
+        if request.user.is_new_user or project.owner != request.user:
             return HttpResponseForbidden()
             
         # Hey, look, we get to update this project.
@@ -85,6 +62,19 @@ class ProjectHandler(BaseHandler):
         
         # PUT returns the newly updated representation
         return self.read(request, slug)
+    
+    @allow_404
+    @authenticated
+    def delete(self, request, slug):
+        project = get_object_or_404(Project, slug=slug)
+        
+        # New users don't get to delete packages; neither do non-owners
+        if request.user.is_new_user or project.owner != request.user:
+            return HttpResponseForbidden()
+            
+        # Otherwise delete it.
+        project.delete()
+        return HttpResponseNoContent()
             
     @classmethod
     def owner(cls, project):</diff>
      <filename>pony_server/handlers.py</filename>
    </modified>
    <modified>
      <diff>@@ -43,7 +43,7 @@ class PonyTests(TestCase):
                 u'name': u'pony',
                 u'owner': u'',
                 u'links': [
-                    {u'allowed_methods': [u'GET', u'PUT'],
+                    {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                      u'href': u'/pony', 
                      u'rel': u'self'},
                     {u'allowed_methods': [u'GET'],
@@ -67,7 +67,7 @@ class PonyTests(TestCase):
             u'name': 'pony',
             u'owner': u'',
             u'links': [
-                {u'allowed_methods': [u'GET', u'PUT'], 
+                {u'allowed_methods': [u'GET', u'PUT', 'DELETE'], 
                  u'href': u'/pony', 
                  u'rel': u'self'},
                 {u'allowed_methods': [u'GET'], 
@@ -98,7 +98,7 @@ class PonyTests(TestCase):
             u'name': u'My Project',
             u'owner': u'testclient',
             u'links': [
-                {u'allowed_methods': [u'GET', u'PUT'],
+                {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                  u'href': u'/proj', 
                  u'rel': u'self'},
                 {u'allowed_methods': [u'GET'],
@@ -136,7 +136,7 @@ class PonyTests(TestCase):
             u'name': u'Renamed',
             u'owner': u'newuser',
             u'links': [
-                {u'allowed_methods': [u'GET', u'PUT'],
+                {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                  u'href': u'/proj', 
                  u'rel': u'self'},
                 {u'allowed_methods': [u'GET'], 
@@ -155,7 +155,48 @@ class PonyTests(TestCase):
                                 content_type=&quot;application/json&quot;,
                                 HTTP_AUTHORIZATION=auth)
         self.assertEqual(r.status_code, 403) # 403 forbidden
+    
+    def test_delete_package_succeeds_when_user_matches(self):
+        # Create a package...
+        auth = &quot;Basic %s&quot; % &quot;newuser:password&quot;.encode(&quot;base64&quot;).strip()
+        r = self.api_client.put('/proj', data='{&quot;name&quot;: &quot;My Project&quot;}', 
+                                content_type=&quot;application/json&quot;,
+                                HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 201) # 201 created
+        
+        # ... and delete it
+        r = self.api_client.delete('/proj', HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 204) # 204 no content
+        
+    def test_delete_package_fails_when_different_user(self):
+        # Create a package...
+        auth = &quot;Basic %s&quot; % &quot;newuser:password&quot;.encode(&quot;base64&quot;).strip()
+        r = self.api_client.put('/proj', data='{&quot;name&quot;: &quot;My Project&quot;}', 
+                                content_type=&quot;application/json&quot;,
+                                HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 201) # 201 created
         
+        # ... and delete it with different auth
+        auth = &quot;Basic %s&quot; % &quot;testclient:password&quot;.encode(&quot;base64&quot;).strip()
+        r = self.api_client.delete('/proj', HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 403) # 403 forbidden
+    
+    def test_delete_package_should_not_create_users(self):
+        # Create a package...
+        auth = &quot;Basic %s&quot; % &quot;newuser:password&quot;.encode(&quot;base64&quot;).strip()
+        r = self.api_client.put('/proj', data='{&quot;name&quot;: &quot;My Project&quot;}', 
+                                content_type=&quot;application/json&quot;,
+                                HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 201) # 201 created
+        
+        # ... and delete it with a new user
+        auth = &quot;Basic %s&quot; % &quot;newuwer2:password&quot;.encode(&quot;base64&quot;).strip()
+        r = self.api_client.delete('/proj', HTTP_AUTHORIZATION=auth)
+        self.assertEqual(r.status_code, 403) # 403 forbidden
+        
+        # newuser2 shouldn't have been created
+        self.assertRaises(User.DoesNotExist, User.objects.get, username='newuser2')
+    
     def test_get_package_build_list(self):
         r = self.api_client.get('/pony/builds')
         self.assertJsonEqual(r, {
@@ -188,7 +229,7 @@ class PonyTests(TestCase):
                     {u'allowed_methods': [u'GET'], 
                      u'href': u'/pony/builds/1', 
                      u'rel': u'self'},
-                    {u'allowed_methods': [u'GET', u'PUT'],
+                    {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                      u'href': u'/pony',
                      u'rel': u'project'},
                     {u'allowed_methods': [u'GET'], 
@@ -200,7 +241,7 @@ class PonyTests(TestCase):
                 ]   
             }],
             u'links': [
-                {u'allowed_methods': [u'GET', u'PUT'],
+                {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                  u'href': u'/pony',
                  u'rel': u'project'},
                 {u'allowed_methods': [u'GET'],
@@ -225,7 +266,7 @@ class PonyTests(TestCase):
                 {u'allowed_methods': [u'GET'], 
                  u'href': u'/pony/tags', 
                  u'rel': u'self'},
-                {u'allowed_methods': [u'GET', u'PUT'],
+                {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                  u'href': u'/pony',
                  u'rel': u'project'},
                 {u'allowed_methods': [u'GET'], 
@@ -270,7 +311,7 @@ class PonyTests(TestCase):
                     {u'allowed_methods': [u'GET'], 
                      u'href': u'/pony/builds/1', 
                      u'rel': u'self'},
-                    {u'allowed_methods': [u'GET', u'PUT'],
+                    {u'allowed_methods': [u'GET', u'PUT', 'DELETE'],
                      u'href': u'/pony',
                      u'rel': u'project'},
                     {u'allowed_methods': [u'GET'], </diff>
      <filename>pony_server/tests.py</filename>
    </modified>
    <modified>
      <diff>@@ -2,14 +2,17 @@
 Piston API helpers.
 &quot;&quot;&quot;
 
+import functools
 import mimeparse
 import piston.resource
 import piston.emitters
 import piston.handler
 import piston.utils
+from django.contrib.auth.models import User
 from django.core import urlresolvers
-from django.http import Http404, HttpResponse, HttpResponseRedirect
-from django.shortcuts import render_to_response, get_object_or_404
+from django.http import (Http404, HttpResponse, HttpResponseRedirect,
+                         HttpResponseForbidden)
+from django.shortcuts import render_to_response
 from django.template import RequestContext
 from django.utils import dateformat
 from django.utils.http import urlencode
@@ -56,6 +59,9 @@ class HttpResponseUnauthorized(HttpResponse):
 
 class HttpResponseCreated(HttpResponseRedirect):
     status_code = 201
+    
+class HttpResponseNoContent(HttpResponse):
+    status_code = 204
 
 def link(rel, to_handler, *args, **getargs):
     &quot;&quot;&quot;
@@ -84,6 +90,48 @@ def allow_404(func):
         except Http404: 
             return piston.utils.rc.NOT_FOUND 
     return wrapper
+    
+def authenticated(callback):
+    &quot;&quot;&quot;
+    Decorate a view method as authenticated.
+    
+    Pony server has somewhat &quot;interesting&quot; authentication: new users are
+    created transparently when creating new resources, so this needs to keep
+    track of whether a user is &quot;new&quot; or not so that the handler may optionally
+    save the user if needed. Thus, this annotates `request.user` with the user
+    (new or not), and sets a `is_new_user` attribute on this user.
+    &quot;&quot;&quot;
+    @functools.wraps(callback)
+    def _pony_auth(self, request, *args, **kwargs):
+        # PUT isn't allowed if we're not authenticated
+        if 'HTTP_AUTHORIZATION' not in request.META:
+            return HttpResponseUnauthorized()
+                
+        # Get or create a user from the Authorization header.
+        try:
+            authtype, auth = request.META['HTTP_AUTHORIZATION'].split(' ')
+            if authtype.lower() != 'basic':
+                return HttpResponseUnauthorized()
+            username, password = auth.decode('base64').split(':')
+            user = User.objects.get(username=username)
+            new_user = False
+        except ValueError:
+            # Raised if split()/unpack fails
+            return HttpResponseUnauthorized()
+        except User.DoesNotExist:
+            user = User(username=username)
+            user.set_password(password)
+            new_user = True
+                    
+        # Make sure the password's correct.
+        if not user.check_password(password):
+            return HttpResponseForbidden()
+            
+        request.user = user
+        request.user.is_new_user = new_user
+        
+        return callback(self, request, *args, **kwargs)
+    return _pony_auth
 
 def format_dt(dt):
     return dateformat.format(dt, 'r')
\ No newline at end of file</diff>
      <filename>pony_server/utils.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>b3aa1489844ec37d9f0698b4d474ff9cb4e1ddaa</id>
    </parent>
  </parents>
  <author>
    <name>Jacob Kaplan-Moss</name>
    <email>jacob@jacobian.org</email>
  </author>
  <url>http://github.com/jacobian/pony_server/commit/cff722b34bf65996f6c0731e5a8c77edb696d7e0</url>
  <id>cff722b34bf65996f6c0731e5a8c77edb696d7e0</id>
  <committed-date>2009-10-26T10:00:13-07:00</committed-date>
  <authored-date>2009-10-26T10:00:13-07:00</authored-date>
  <message>Implemented DELETE /{project}.

As part of this, roughly broke the auth stuff out into a decorator. I'm still
not sure this is the final resting place for the auth code, but it'll work.</message>
  <tree>fcdebaf2cce49b7a519262259e14f5480732506f</tree>
  <committer>
    <name>Jacob Kaplan-Moss</name>
    <email>jacob@jacobian.org</email>
  </committer>
</commit>
