Jesper Nøhr avatar Jesper Nøhr committed 4874292

support for multiple authenticators per resource w/ testcase and backwards compat

Comments (0)

Files changed (4)

piston/authentication.py

         
         request.user = self.auth_func(username=username, password=password) \
             or AnonymousUser()
-        
+                
         return not request.user in (False, None, AnonymousUser())
         
     def challenge(self):
         resp.status_code = 401
         return resp
 
+    def __repr__(self):
+        return u'<HTTPBasic: realm=%s>' % self.realm
+
+class HttpBasicSimple(HttpBasicAuthentication):
+    def __init__(self, realm, username, password):
+        self.user = User.objects.get(username=username)
+        self.password = password
+
+        super(HttpBasicSimple, self).__init__(auth_func=self.hash, realm=realm)
+    
+    def hash(self, username, password):
+        if username == self.user.username and password == self.password:
+            return self.user
+
 def load_data_store():
     '''Load data store for OAuth Consumers, Tokens, Nonces and Resources
     '''

piston/resource.py

 from utils import coerce_put_post, FormValidationError, HttpStatusCode
 from utils import rc, format_error, translate_mime, MimerDataException
 
+CHALLENGE = object()
+
 class Resource(object):
     """
     Resource. Create one for your URL mappings, just
         self.handler = handler()
         
         if not authentication:
-            self.authentication = NoAuthentication()
+            self.authentication = (NoAuthentication(),)
+        elif isinstance(authentication, list):
+            self.authentication = authentication
         else:
-            self.authentication = authentication
+            self.authentication = (authentication,)
             
         # Erroring
         self.email_errors = getattr(settings, 'PISTON_EMAIL_ERRORS', True)
             
         return None
     
+    def authenticate(self, request, rm):
+        actor, anonymous = False, True
+
+        for authenticator in self.authentication:
+            if not authenticator.is_authenticated(request):
+                if self.anonymous and \
+                    rm in self.anonymous.allowed_methods:
+
+                    actor, anonymous = self.anonymous(), True
+                else:
+                    actor, anonymous = authenticator.challenge, CHALLENGE
+            else:
+                return self.handler, self.handler.is_anonymous
+        
+        return actor, anonymous
+    
     @vary_on_headers('Authorization')
     def __call__(self, request, *args, **kwargs):
         """
         if rm == "PUT":
             coerce_put_post(request)
 
-        if not self.authentication.is_authenticated(request):
-            if self.anonymous and \
-                rm in self.anonymous.allowed_methods:
+        actor, anonymous = self.authenticate(request, rm)
 
-                handler = self.anonymous()
-                anonymous = True
-            else:
-                return self.authentication.challenge()
+        if anonymous is CHALLENGE:
+            return actor()
         else:
-            handler = self.handler
-            anonymous = handler.is_anonymous
+            handler = actor
         
         # Translate nested datastructs into `request.data` here.
         if rm in ('POST', 'PUT'):

tests/test_project/apps/testapp/tests.py

             HTTP_AUTHORIZATION=bad_auth_string)
         self.assertEquals(response.status_code, 401)
 
+class TestMultipleAuthenticators(MainTests):
+    def test_both_authenticators(self):
+        for username, password in (('admin', 'admin'), 
+                                   ('admin', 'secr3t'),
+                                   ('admin', 'user'),
+                                   ('admin', 'allwork'),
+                                   ('admin', 'thisisneat')):
+            auth_string = 'Basic %s' % base64.encodestring('%s:%s' % (username, password)).rstrip()
+
+            response = self.client.get('/api/multiauth/',
+                HTTP_AUTHORIZATION=auth_string)
+
+            self.assertEquals(response.status_code, 200, 'Failed with combo of %s:%s' % (username, password))
+
 class MultiXMLTests(MainTests):
     def init_delegate(self):
         self.t1_data = TestModel()

tests/test_project/apps/testapp/urls.py

 from django.conf.urls.defaults import *
 from piston.resource import Resource
-from piston.authentication import HttpBasicAuthentication
+from piston.authentication import HttpBasicAuthentication, HttpBasicSimple
 
 from test_project.apps.testapp.handlers import EntryHandler, ExpressiveHandler, AbstractHandler, EchoHandler, PlainOldObjectHandler, Issue58Handler, ListFieldsHandler
 
 list_fields = Resource(handler=ListFieldsHandler)
 issue58 = Resource(handler=Issue58Handler)
 
+AUTHENTICATORS = [auth,]
+SIMPLE_USERS = (('admin', 'secr3t'),
+                ('admin', 'user'),
+                ('admin', 'allwork'),
+                ('admin', 'thisisneat'))
+
+for username, password in SIMPLE_USERS:
+    AUTHENTICATORS.append(HttpBasicSimple(realm='Test', 
+                            username=username, password=password))
+
+multiauth = Resource(handler=PlainOldObjectHandler, 
+                        authentication=AUTHENTICATORS)
+
 urlpatterns = patterns('',
     url(r'^entries/$', entries),
     url(r'^entries/(?P<pk>.+)/$', entries),
 
     url(r'^echo$', echo),
 
+    url(r'^multiauth/$', multiauth),
+
     # oauth entrypoints
     url(r'^oauth/request_token$', 'piston.authentication.oauth_request_token'),
     url(r'^oauth/authorize$', 'piston.authentication.oauth_user_auth'),
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.