root/oss/httpauthfilter/test.py

Revision 172, 9.6 kB (checked in by sylvain, 3 years ago)

* Added support for hashed passwords (md5)
* Fixed static content handling

Line 
1 # -*- coding: utf-8 -*-
2
3 import cherrypy
4 import httpauth
5 from httpauthfilter import HttpAuthFilter
6
7 def retrieveAuthUsers():
8     return {'test':'test'}
9 def retrieveAuthUsersWithRealms():
10     return {'test':{'profiles@localhost':
11                     '2fc6fc613a119b1f67d3598e998b347d'}}
12
13 class Son:
14     def index(self):
15         '''Protected by the filter above'''
16         return "Hello son!"
17     index.exposed = True
18
19     def hello(self, name):
20         return "Hello %s" % (name, )
21     hello.exposed = True
22
23 class Admin:
24
25     _cp_filters = [ HttpAuthFilter(realm='admin@localhost',
26                                    privateKey='duh!',
27                                    unauthorizedPath='/admin/unauthorized',
28                                    retrieveUsersFunc=retrieveAuthUsers) ]
29     def __init__(self):
30         self.son = Son()
31
32     def index(self):
33         '''Only available once the user is authenticated'''
34         return "Hello there!"
35     index.exposed = True
36
37     def unauthorized(self, *args, **kwargs):
38         '''Must be declared at the same level where you defined the filter'''
39         return "You are not authorized to access that page"
40     unauthorized.exposed = True
41
42 class Profiles:
43
44     _cp_filters = [ HttpAuthFilter(realm='profiles@localhost',
45                                    privateKey='duh!',
46                                    unauthorizedPath='/profiles/unauthorized',
47                                    retrieveUsersFunc=retrieveAuthUsersWithRealms,
48                                    storeHashedCredentials=True) ]
49
50     def __init__(self):
51         pass
52
53     def index(self):
54         '''Only available once the user is authenticated'''
55         return "Hello there!"
56     index.exposed = True
57
58     def unauthorized(self, *args, **kwargs):
59         '''Must be declared at the same level where you defined the filter'''
60         return "You are not authorized to access that page"
61     unauthorized.exposed = True
62
63
64 class Root:
65     def __init__(self):
66         self.admin = Admin()
67         self.profiles = Profiles()
68
69     def index(self):
70         return "hey how you doing?"
71     index.exposed = True
72
73 conf = {'global': { 'log_debug_info_filter.on': False,
74                     'autoreload.on': False,
75                     'server.log_to_screen': False,}}
76 cherrypy.tree.mount(Root(), '/', conf=conf)
77
78 from cherrypy.test import helper
79
80 class HttpAuthFilter(helper.CPWebCase):
81
82    def testBasicAuth(self):
83        self.getPage('/admin/')
84        self.assertStatus('401 Unauthorized')
85        self.assertHeader("WWW-Authenticate", 'Basic realm="admin@localhost"')
86        self.assertBody("You are not authorized to access that page")
87
88        # make sure we keep getting that response as long as we haven't
89        # given the authentification token
90        self.getPage('/admin/')
91        self.assertStatus('401 Unauthorized')
92        self.assertHeader("WWW-Authenticate", 'Basic realm="admin@localhost"')
93
94        # right show our ID
95        self.getPage('/admin/', [('Authorization', 'Basic dGVzdDp0ZXN0')])
96        self.assertStatus('200 OK')
97        self.assertBody("Hello there!")
98
99        # should work also through the tree below the admin instance
100        self.getPage('/admin/son/', [('Authorization', 'Basic dGVzdDp0ZXN0')])
101        self.assertStatus('200 OK')
102
103        # should forbid us again
104        self.getPage('/admin/')
105        self.assertStatus('401 Unauthorized')
106        self.assertHeader("WWW-Authenticate", 'Basic realm="admin@localhost"')
107        self.assertBody("You are not authorized to access that page")
108
109    def testDigestAuth(self):
110        self.getPage('/admin/')
111        self.assertStatus('401 Unauthorized')
112        self.assertHeader("WWW-Authenticate")
113        self.assertBody("You are not authorized to access that page")
114
115        # the filter returns two WWW-Authenticate
116        # the digest auth should be the first one though
117        lowkey = "www-authenticate"
118        value = None
119        for k, v in self.headers:
120            if k.lower() == lowkey:
121                if v.startswith("Digest"):
122                    value = v
123                    break
124
125        if value is None:
126            self._handlewebError("Digest authentification scheme was not found")
127
128        # the digest algorithm uses a timestamp
129        # so we need to parse the response ourself because we can't hardcode
130        # the expected value
131        if value:
132            value = value[7:]
133            items = value.split(', ')
134            tokens = {}
135            for item in items:
136                key, value = item.split('=')
137                tokens[key.lower()] = value
138
139            missing_msg = "%s is missing"
140            bad_value_msg = "'%s' was expecting '%s' but found '%s'"
141            nonce = None
142            if 'realm' not in tokens:
143                self._handlewebError(missing_msg % 'realm')
144            elif tokens['realm'] != '"admin@localhost"':
145                self._handlewebError(bad_value_msg % ('realm', '"admin@localhost"', tokens['realm']))
146            if 'nonce' not in tokens:
147                self._handlewebError(missing_msg % 'nonce')
148            else:
149                nonce = tokens['nonce'].strip('"')
150            if 'algorithm' not in tokens:
151                self._handlewebError(missing_msg % 'algorithm')
152            elif tokens['algorithm'] != '"MD5"':
153                self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
154            if 'qop' not in tokens:
155                self._handlewebError(missing_msg % 'qop')
156            elif tokens['qop'] != '"auth"':
157                self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
158
159            # now let's see if what
160            base_auth = 'Digest username="test", realm="admin@localhost", nonce="%s", uri="/admin/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
161
162            auth = base_auth % (nonce, '', '00000001')
163
164            params = httpauth.parseAuthorization(auth)
165            response = httpauth._computeDigestResponse(params, 'test')
166
167            auth = base_auth % (nonce, response, '00000001')
168            self.getPage('/admin/', [('Authorization', auth)])
169            self.assertStatus('200 OK')
170
171    def testBasicAuthWithHashedPasswords(self):
172        self.getPage('/profiles/')
173        self.assertStatus('401 Unauthorized')
174        self.assertHeader("WWW-Authenticate",
175                          'Basic realm="profiles@localhost"')
176        self.assertBody("You are not authorized to access that page")
177
178        # make sure we keep getting that response as long as we haven't
179        # given the authentification token
180        self.getPage('/profiles/')
181        self.assertStatus('401 Unauthorized')
182        self.assertHeader("WWW-Authenticate",
183                          'Basic realm="profiles@localhost"')
184
185        # right show our ID
186        self.getPage('/profiles/', [('Authorization', 'Basic dGVzdDp0ZXN0')])
187        self.assertStatus('200 OK')
188        self.assertBody("Hello there!")
189
190        # should forbid us again
191        self.getPage('/profiles/')
192        self.assertStatus('401 Unauthorized')
193        self.assertHeader("WWW-Authenticate", 'Basic realm="profiles@localhost"')
194        self.assertBody("You are not authorized to access that page")
195
196    def testDigestAuthWithHashedPasswords(self):
197        self.getPage('/profiles/')
198        self.assertStatus('401 Unauthorized')
199        self.assertHeader("WWW-Authenticate")
200        self.assertBody("You are not authorized to access that page")
201
202        # the filter returns two WWW-Authenticate
203        # the digest auth should be the first one though
204        lowkey = "www-authenticate"
205        value = None
206        for k, v in self.headers:
207            if k.lower() == lowkey:
208                if v.startswith("Digest"):
209                    value = v
210                    break
211
212        if value is None:
213            self._handlewebError("Digest authentification scheme was not found")
214
215        # the digest algorithm uses a timestamp
216        # so we need to parse the response ourself because we can't hardcode
217        # the expected value
218        if value:
219            value = value[7:]
220            items = value.split(', ')
221            tokens = {}
222            for item in items:
223                key, value = item.split('=')
224                tokens[key.lower()] = value
225
226            missing_msg = "%s is missing"
227            bad_value_msg = "'%s' was expecting '%s' but found '%s'"
228            nonce = None
229            if 'realm' not in tokens:
230                self._handlewebError(missing_msg % 'realm')
231            elif tokens['realm'] != '"profiles@localhost"':
232                self._handlewebError(bad_value_msg % ('realm', '"profiles@localhost"', tokens['realm']))
233            if 'nonce' not in tokens:
234                self._handlewebError(missing_msg % 'nonce')
235            else:
236                nonce = tokens['nonce'].strip('"')
237            if 'algorithm' not in tokens:
238                self._handlewebError(missing_msg % 'algorithm')
239            elif tokens['algorithm'] != '"MD5"':
240                self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
241            if 'qop' not in tokens:
242                self._handlewebError(missing_msg % 'qop')
243            elif tokens['qop'] != '"auth"':
244                self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
245
246            # now let's see if what
247            base_auth = 'Digest username="test", realm="profiles@localhost", nonce="%s", uri="/profiles/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
248
249            auth = base_auth % (nonce, '', '00000001')
250
251            params = httpauth.parseAuthorization(auth)
252            response = httpauth._computeDigestResponse(params, 'test')
253
254            auth = base_auth % (nonce, response, '00000001')
255            self.getPage('/profiles/', [('Authorization', auth)])
256            self.assertStatus('200 OK')
257
258 if __name__ == '__main__':
259     helper.testmain()
260                    
Note: See TracBrowser for help on using the browser.