• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.PluggableAuthService / 5303493172

pending completion
5303493172

push

github

web-flow
Drop support for Python 2.7, 3.5, 3.6. (#116)

* Drop zserver extra in setup.py. Thus dropping FTP support.
* Drop support for Zope < 5.
Co-authored-by: Jens Vagelpohl <jens@plyp.com>

1288 of 1745 branches covered (73.81%)

Branch coverage included in aggregate %.

127 of 127 new or added lines in 30 files covered. (100.0%)

9619 of 10349 relevant lines covered (92.95%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/src/Products/PluggableAuthService/plugins/tests/test_CookieAuthHelper.py
1
##############################################################################
2
#
3
# Copyright (c) 2001 Zope Foundation and Contributors
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this
7
# distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
12
#
13
##############################################################################
14
import codecs
1✔
15
import unittest
1✔
16
from base64 import encodebytes
1✔
17

18
from ...interfaces.plugins import IChallengePlugin
1✔
19
from ...tests import pastc
1✔
20
from ...tests.conformance import IChallengePlugin_conformance
1✔
21
from ...tests.conformance import ICredentialsResetPlugin_conformance
1✔
22
from ...tests.conformance import ICredentialsUpdatePlugin_conformance
1✔
23
from ...tests.conformance import ILoginPasswordHostExtractionPlugin_conformance
1✔
24
from ...tests.test_PluggableAuthService import FauxContainer
1✔
25
from ...tests.test_PluggableAuthService import FauxObject
1✔
26
from ...tests.test_PluggableAuthService import FauxRequest
1✔
27
from ...tests.test_PluggableAuthService import FauxResponse
1✔
28
from ...tests.test_PluggableAuthService import FauxRoot
1✔
29

30

31
class FauxSettableRequest(FauxRequest):
1✔
32

33
    def set(self, name, value):
1✔
34
        self._dict[name] = value
1✔
35

36

37
class FauxCookieResponse(FauxResponse):
1✔
38

39
    def __init__(self):
1✔
40
        self.cookies = {}
1✔
41
        self.cookie_attributes = {}
1✔
42
        self.redirected = False
1✔
43
        self.status = '200'
1✔
44
        self.headers = {}
1✔
45

46
    def setCookie(self, cookie_name, cookie_value, path, **kw):
1✔
47
        self.cookies[(cookie_name, path)] = cookie_value
1✔
48
        self.cookie_attributes[(cookie_name, path)] = kw
1✔
49

50
    def expireCookie(self, cookie_name, path):
1✔
51
        if (cookie_name, path) in self.cookies:
1!
52
            del self.cookies[(cookie_name, path)]
×
53

54
    def redirect(self, location, status=302, lock=0):
1✔
55
        self.status = status
1✔
56
        self.headers['Location'] = location
1✔
57

58
    def setHeader(self, name, value):
1✔
59
        self.headers[name] = value
1✔
60

61

62
class CookieAuthHelperTests(unittest.TestCase,
1✔
63
                            ILoginPasswordHostExtractionPlugin_conformance,
64
                            IChallengePlugin_conformance,
65
                            ICredentialsResetPlugin_conformance,
66
                            ICredentialsUpdatePlugin_conformance):
67

68
    def _getTargetClass(self):
1✔
69

70
        from ...plugins.CookieAuthHelper import CookieAuthHelper
1✔
71

72
        return CookieAuthHelper
1✔
73

74
    def _makeOne(self, id='test', *args, **kw):
1✔
75

76
        return self._getTargetClass()(id=id, *args, **kw)
1✔
77

78
    def _makeTree(self):
1✔
79

80
        rc = FauxObject('rc')
1✔
81
        root = FauxRoot('root').__of__(rc)
1✔
82
        folder = FauxContainer('folder').__of__(root)
1✔
83
        object = FauxObject('object').__of__(folder)
1✔
84

85
        return rc, root, folder, object
1✔
86

87
    def test_extractCredentials_no_creds(self):
1✔
88

89
        helper = self._makeOne()
1✔
90
        response = FauxCookieResponse()
1✔
91
        request = FauxRequest(RESPONSE=response)
1✔
92

93
        self.assertEqual(helper.extractCredentials(request), {})
1✔
94

95
    def test_extractCredentials_with_form_creds(self):
1✔
96

97
        helper = self._makeOne()
1✔
98
        response = FauxCookieResponse()
1✔
99
        request = FauxSettableRequest(__ac_name='foo',
1✔
100
                                      __ac_password='b:ar',
101
                                      RESPONSE=response)
102

103
        self.assertEqual(len(response.cookies), 0)
1✔
104
        self.assertEqual(helper.extractCredentials(request),
1✔
105
                         {'login': 'foo',
106
                          'password': 'b:ar',
107
                          'remote_host': '',
108
                          'remote_address': ''})
109
        self.assertEqual(len(response.cookies), 0)
1✔
110

111
    def test_extractCredentials_with_deleted_cookie(self):
1✔
112
        # http://www.zope.org/Collectors/PAS/43
113
        # Edge case: The ZPublisher sets a cookie's value to "deleted"
114
        # in the current request if expireCookie is called. If we hit
115
        # extractCredentials in the same request after this, it would
116
        # blow up trying to deal with the invalid cookie value.
117
        helper = self._makeOne()
1✔
118
        response = FauxCookieResponse()
1✔
119
        req_data = {helper.cookie_name: 'deleted', 'RESPONSE': response}
1✔
120
        request = FauxSettableRequest(**req_data)
1✔
121
        self.assertEqual(len(response.cookies), 0)
1✔
122

123
        self.assertEqual(helper.extractCredentials(request), {})
1✔
124

125
    def test_challenge(self):
1✔
126
        rc, root, folder, object = self._makeTree()
1✔
127
        response = FauxCookieResponse()
1✔
128
        testPath = '/some/path?arg1=val1&arg2=val2'
1✔
129
        testURL = 'http://test' + testPath
1✔
130
        request = FauxRequest(RESPONSE=response, URL=testURL,
1✔
131
                              ACTUAL_URL=testURL)
132
        root.REQUEST = request
1✔
133

134
        helper = self._makeOne().__of__(root)
1✔
135

136
        helper.challenge(request, response)
1✔
137
        self.assertEqual(response.status, 302)
1✔
138
        self.assertEqual(len(response.headers), 3)
1✔
139
        self.assertEqual(
1✔
140
            response.headers['Location'],
141
            '/login_form?came_from=/some/path%3Farg1%3Dval1%26arg2%3Dval2')
142
        self.assertEqual(response.headers['Cache-Control'], 'no-cache')
1✔
143
        self.assertEqual(response.headers['Expires'],
1✔
144
                         'Sat, 01 Jan 2000 00:00:00 GMT')
145

146
    def test_challenge_with_vhm(self):
1✔
147
        rc, root, folder, object = self._makeTree()
1✔
148
        response = FauxCookieResponse()
1✔
149
        vhm = 'http://localhost/VirtualHostBase/http/test/VirtualHostRoot/xxx'
1✔
150
        actualURL = 'http://test/xxx?arg1=val1&arg2=val2'
1✔
151

152
        request = FauxRequest(RESPONSE=response, URL=vhm,
1✔
153
                              ACTUAL_URL=actualURL)
154
        root.REQUEST = request
1✔
155

156
        helper = self._makeOne().__of__(root)
1✔
157

158
        helper.challenge(request, response)
1✔
159
        self.assertEqual(response.status, 302)
1✔
160
        self.assertEqual(len(response.headers), 3)
1✔
161
        self.assertEqual(
1✔
162
            response.headers['Location'],
163
            '/login_form?came_from=/xxx%3Farg1%3Dval1%26arg2%3Dval2')
164
        self.assertEqual(response.headers['Cache-Control'], 'no-cache')
1✔
165
        self.assertEqual(response.headers['Expires'],
1✔
166
                         'Sat, 01 Jan 2000 00:00:00 GMT')
167

168
    def test_resetCredentials(self):
1✔
169
        helper = self._makeOne()
1✔
170
        response = FauxCookieResponse()
1✔
171
        request = FauxRequest(RESPONSE=response)
1✔
172

173
        helper.resetCredentials(request, response)
1✔
174
        self.assertEqual(len(response.cookies), 0)
1✔
175

176
    def test_login_redirect(self):
1✔
177
        helper = self._makeOne()
1✔
178
        response = FauxCookieResponse()
1✔
179
        request = FauxSettableRequest(RESPONSE=response)
1✔
180
        helper.REQUEST = request
1✔
181

182
        # came_from empty, redirect is empty as well
183
        url = ''
1✔
184
        request.form['came_from'] = url
1✔
185
        helper.login()
1✔
186
        self.assertEqual(response.headers['Location'], url)
1✔
187

188
        # came_from is site-local, redirect won't change the URL
189
        url = '/foo?arg1=val1&arg2=val2'
1✔
190
        request.form['came_from'] = url
1✔
191
        helper.login()
1✔
192
        self.assertEqual(response.headers['Location'], url)
1✔
193

194
        # Protocol and host parts will be chopped off
195
        url = 'http://evil.com/foo?arg1=val1&arg2=val2'
1✔
196
        request.form['came_from'] = url
1✔
197
        helper.login()
1✔
198
        self.assertEqual(response.headers['Location'],
1✔
199
                         '/foo?arg1=val1&arg2=val2')
200

201
    def test_loginWithoutCredentialsUpdate(self):
1✔
202
        helper = self._makeOne()
1✔
203
        response = FauxCookieResponse()
1✔
204
        request = FauxSettableRequest(__ac_name='foo', __ac_password='bar',
1✔
205
                                      RESPONSE=response)
206
        request.form['came_from'] = ''
1✔
207
        helper.REQUEST = request
1✔
208

209
        helper.login()
1✔
210
        self.assertEqual(len(response.cookies), 0)
1✔
211

212
    def test_extractCredentials_from_cookie_with_colon_in_password(self):
1✔
213
        # http://www.zope.org/Collectors/PAS/51
214
        # Passwords with ":" characters broke authentication
215
        helper = self._makeOne()
1✔
216
        response = FauxCookieResponse()
1✔
217
        request = FauxSettableRequest(RESPONSE=response)
1✔
218

219
        username = codecs.encode(b'foo', 'hex_codec')
1✔
220
        password = codecs.encode(b'b:ar', 'hex_codec')
1✔
221
        cookie_str = b'%s:%s' % (username, password)
1✔
222
        cookie_val = encodebytes(cookie_str)
1✔
223
        cookie_val = cookie_val.rstrip()
1✔
224
        cookie_val = cookie_val.decode('utf8')
1✔
225
        request.set(helper.cookie_name, cookie_val)
1✔
226

227
        self.assertEqual(helper.extractCredentials(request),
1✔
228
                         {'login': 'foo',
229
                          'password': 'b:ar',
230
                          'remote_host': '',
231
                          'remote_address': ''})
232

233
    def test_extractCredentials_from_cookie_with_colon_that_is_not_ours(self):
1✔
234
        # http://article.gmane.org/gmane.comp.web.zope.plone.product-developers/5145
235
        helper = self._makeOne()
1✔
236
        response = FauxCookieResponse()
1✔
237
        request = FauxSettableRequest(RESPONSE=response)
1✔
238

239
        cookie_str = b'cookie:from_other_plugin'
1✔
240
        cookie_val = encodebytes(cookie_str)
1✔
241
        cookie_val = cookie_val.rstrip()
1✔
242
        cookie_val = cookie_val.decode('utf8')
1✔
243
        request.set(helper.cookie_name, cookie_val)
1✔
244

245
        self.assertEqual(helper.extractCredentials(request), {})
1✔
246

247
    def test_extractCredentials_from_cookie_with_bad_binascii(self):
1✔
248
        # this might happen between browser implementations
249
        helper = self._makeOne()
1✔
250
        response = FauxCookieResponse()
1✔
251
        request = FauxSettableRequest(RESPONSE=response)
1✔
252

253
        cookie_val = 'NjE2NDZkNjk2ZTo3MDZjNmY2ZTY1MzQ3NQ%3D%3D'[:-1]
1✔
254
        request.set(helper.cookie_name, cookie_val)
1✔
255

256
        self.assertEqual(helper.extractCredentials(request), {})
1✔
257

258
    def test_updateCredentials(self):
1✔
259
        helper = self._makeOne()
1✔
260
        response = FauxCookieResponse()
1✔
261
        request = FauxSettableRequest(RESPONSE=response)
1✔
262

263
        username = codecs.encode(b'foo', 'hex_codec')
1✔
264
        password = codecs.encode(b'b:ar', 'hex_codec')
1✔
265
        cookie_str = b'%s:%s' % (username, password)
1✔
266
        cookie_val = encodebytes(cookie_str)
1✔
267
        cookie_val = cookie_val.rstrip()
1✔
268
        cookie_val = cookie_val.decode('utf8')
1✔
269
        request.set(helper.cookie_name, cookie_val)
1✔
270

271
        # Defaults
272
        helper.updateCredentials(request, response, 'new_user', 'new_pass')
1✔
273
        cookie_attrs = response.cookie_attributes[(helper.cookie_name, '/')]
1✔
274
        self.assertEqual(cookie_attrs['same_site'], 'Lax')
1✔
275
        self.assertFalse(cookie_attrs['secure'])
1✔
276

277
        # Setting the cookie same site value to "None" forces secure flag
278
        helper.cookie_same_site = 'None'
1✔
279
        helper.updateCredentials(request, response, 'new_user', 'new_pass')
1✔
280
        cookie_attrs = response.cookie_attributes[(helper.cookie_name, '/')]
1✔
281
        self.assertEqual(cookie_attrs['same_site'], 'None')
1✔
282
        self.assertTrue(cookie_attrs['secure'])
1✔
283

284
        # Setting the Secure flag manually
285
        helper.cookie_same_site = 'Lax'
1✔
286
        helper.cookie_secure = True
1✔
287
        helper.updateCredentials(request, response, 'new_user', 'new_pass')
1✔
288
        cookie_attrs = response.cookie_attributes[(helper.cookie_name, '/')]
1✔
289
        self.assertEqual(cookie_attrs['same_site'], 'Lax')
1✔
290
        self.assertTrue(cookie_attrs['secure'])
1✔
291

292

293
class CookieAuthHelperIntegrationTests(pastc.PASTestCase):
1✔
294

295
    def test_login_with_missing_came_from(self):
1✔
296
        pas = self.folder.acl_users
1✔
297
        factory = pas.manage_addProduct['PluggableAuthService']
1✔
298
        factory.addCookieAuthHelper('cookie_auth')
1✔
299
        plugins = pas.plugins
1✔
300
        plugins.activatePlugin(IChallengePlugin, 'cookie_auth')
1✔
301

302
        response = FauxCookieResponse()
1✔
303
        request = FauxSettableRequest(RESPONSE=response)
1✔
304

305
        # find cookie auth
306
        for id_, plugin in pas.plugins.items():
1!
307
            if id_ == 'cookie_auth':
1✔
308
                cookie_auth = plugin
1✔
309
                break
1✔
310

311
        cookie_auth.REQUEST = request
1✔
312
        cookie_auth.login()
1✔
313

314
        self.assertEqual(
1✔
315
            response.headers['Location'], 'http://nohost/test_folder_1_')
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc