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

zopefoundation / Products.Sessions / 16399689495

05 Apr 2025 07:12AM UTC coverage: 94.731%. Remained the same
16399689495

push

github

dataflake
- vb [ci skip]

146 of 180 branches covered (81.11%)

Branch coverage included in aggregate %.

1526 of 1585 relevant lines covered (96.28%)

0.96 hits per line

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

93.15
/src/Products/Sessions/BrowserIdManager.py
1
############################################################################
2
#
3
# Copyright (c) 2002 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 distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
############################################################################
13

14
import binascii
1✔
15
import logging
1✔
16
import os
1✔
17
# Use the system PRNG if possible
18
import random
1✔
19
import re
1✔
20
import sys
1✔
21
import time
1✔
22
from hashlib import sha256
1✔
23
from html import escape
1✔
24
from urllib.parse import quote
1✔
25
from urllib.parse import urlparse
1✔
26
from urllib.parse import urlunparse
1✔
27

28
from AccessControl.class_init import InitializeClass
1✔
29
from AccessControl.Permissions import access_contents_information
1✔
30
from AccessControl.Permissions import view_management_screens
1✔
31
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
32
from Acquisition import Implicit
1✔
33
from Acquisition import aq_inner
1✔
34
from Acquisition import aq_parent
1✔
35
from App.Management import Tabs
1✔
36
from App.special_dtml import DTMLFile
1✔
37
from OFS.owner import Owned
1✔
38
from OFS.role import RoleManager
1✔
39
from OFS.SimpleItem import Item
1✔
40
from Persistence import Persistent
1✔
41
from persistent import TimeStamp
1✔
42
from zope.interface import implementer
1✔
43
from ZPublisher.BeforeTraverse import queryBeforeTraverse
1✔
44
from ZPublisher.BeforeTraverse import registerBeforeTraverse
1✔
45
from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
1✔
46

47
from .interfaces import BrowserIdManagerErr
1✔
48
from .interfaces import IBrowserIdManager
1✔
49
from .permissions import change_browser_id_managers
1✔
50

51

52
badidnamecharsin = re.compile(r'[\?&;,<> ]').search
1✔
53
badcookiecharsin = re.compile(r'[;,<>& ]').search
1✔
54
twodotsin = re.compile(r'(\w*\.){2,}').search
1✔
55

56
_marker = []
1✔
57

58
constructBrowserIdManagerForm = DTMLFile('dtml/addIdManager', globals())
1✔
59

60
BROWSERID_MANAGER_NAME = 'browser_id_manager'  # imported by SessionDataManager
1✔
61
ALLOWED_BID_NAMESPACES = ('form', 'cookies', 'url')
1✔
62
TRAVERSAL_APPHANDLE = 'BrowserIdManager'
1✔
63

64
LOG = logging.getLogger('Zope.BrowserIdManager')
1✔
65

66
try:
1✔
67
    random = random.SystemRandom()
1✔
68
    using_sysrandom = True
1✔
69
except NotImplementedError:
×
70
    using_sysrandom = False
×
71

72

73
def _randint(start, end):
1✔
74
    if not using_sysrandom:
1!
75
        # This is ugly, and a hack, but it makes things better than
76
        # the alternative of predictability. This re-seeds the PRNG
77
        # using a value that is hard for an attacker to predict, every
78
        # time a random string is required. This may change the
79
        # properties of the chosen random sequence slightly, but this
80
        # is better than absolute predictability.
81
        random.seed(
×
82
            sha256(f"{random.getstate()}{time.time()}{os.getpid()}").digest())
83
    return random.randint(start, end)
1✔
84

85

86
def constructBrowserIdManager(self,
1✔
87
                              id=BROWSERID_MANAGER_NAME,
88
                              title='',
89
                              idname='_ZopeId',
90
                              location=('cookies', 'form'),
91
                              cookiepath='/',
92
                              cookiedomain='',
93
                              cookielifedays=0,
94
                              cookiesecure=0,
95
                              cookiehttponly=0,
96
                              auto_url_encoding=0,
97
                              cookiesamesite='Lax',
98
                              REQUEST=None):
99
    """ """
100
    ob = BrowserIdManager(id, title, idname, location, cookiepath,
×
101
                          cookiedomain, cookielifedays, cookiesecure,
102
                          cookiehttponly, auto_url_encoding, cookiesamesite)
103
    self._setObject(id, ob)
×
104
    ob = self._getOb(id)
×
105
    if REQUEST is not None:
×
106
        return self.manage_main(self, REQUEST, update_menu=1)
×
107

108

109
@implementer(IBrowserIdManager)
1✔
110
class BrowserIdManager(Item, Persistent, Implicit, RoleManager, Owned, Tabs):
1✔
111
    """ browser id management class
112
    """
113
    meta_type = 'Browser Id Manager'
1✔
114
    zmi_icon = 'far fa-id-card'
1✔
115

116
    security = ClassSecurityInfo()
1✔
117
    security.declareObjectPublic()
1✔
118
    ok = {
1✔
119
        'meta_type': 1,
120
        'id': 1,
121
        'title': 1,
122
        'zmi_icon': 1,
123
        'title_or_id': 1,
124
    }
125
    security.setDefaultAccess(ok)
1✔
126
    security.setPermissionDefault(view_management_screens, ['Manager'])
1✔
127
    security.setPermissionDefault(
1✔
128
        access_contents_information,
129
        ['Manager', 'Anonymous'],
130
    )
131
    security.setPermissionDefault(change_browser_id_managers, ['Manager'])
1✔
132

133
    # BBB
134
    auto_url_encoding = 0
1✔
135
    cookie_http_only = 0
1✔
136
    cookie_same_site = None
1✔
137

138
    def __init__(
1✔
139
        self,
140
        id,
141
        title='',
142
        idname='_ZopeId',
143
        location=('cookies', 'form'),
144
        cookiepath=('/'),
145
        cookiedomain='',
146
        cookielifedays=0,
147
        cookiesecure=0,
148
        cookiehttponly=0,
149
        auto_url_encoding=0,
150
        cookiesamesite='Lax',
151
    ):
152
        self.id = str(id)
1✔
153
        self.title = str(title)
1✔
154
        self.setBrowserIdName(idname)
1✔
155
        self.setBrowserIdNamespaces(location)
1✔
156
        self.setCookiePath(cookiepath)
1✔
157
        self.setCookieDomain(cookiedomain)
1✔
158
        self.setCookieLifeDays(cookielifedays)
1✔
159
        self.setCookieSecure(cookiesecure)
1✔
160
        self.setCookieHTTPOnly(cookiehttponly)
1✔
161
        self.setAutoUrlEncoding(auto_url_encoding)
1✔
162
        self.setCookieSameSite(cookiesamesite)
1✔
163

164
    # IBrowserIdManager
165
    @security.protected(access_contents_information)
1✔
166
    def hasBrowserId(self):
1✔
167
        """ See IBrowserIdManager.
168
        """
169
        try:
1✔
170
            return self.getBrowserId(create=0) is not None
1✔
171
        except BrowserIdManagerErr:
1✔
172
            return False
1✔
173

174
    @security.protected(access_contents_information)
1✔
175
    def getBrowserId(self, create=1):
1✔
176
        """ See IBrowserIdManager.
177
        """
178
        REQUEST = self.REQUEST
1✔
179
        # let's see if bid has already been attached to request
180
        bid = getattr(REQUEST, 'browser_id_', None)
1✔
181
        if bid is not None:
1✔
182
            # it's already set in this request so we can just return it
183
            # if it's well-formed
184
            if not isAWellFormedBrowserId(bid):
1✔
185
                # somebody screwed with the REQUEST instance during
186
                # this request.
187
                raise BrowserIdManagerErr(
1✔
188
                    'Ill-formed browserid in REQUEST.browser_id_:  %s' %
189
                    escape(bid))
190
            return bid
1✔
191
        # fall through & ck form/cookie namespaces if bid is not in request.
192
        tk = self.browserid_name
1✔
193
        ns = self.browserid_namespaces
1✔
194
        for name in ns:
1✔
195
            if name == 'url':
1!
196
                continue  # browser ids in url are checked by Traverser class
×
197
            current_ns = getattr(REQUEST, name, None)
1✔
198
            if current_ns is None:
1✔
199
                continue
1✔
200
            bid = current_ns.get(tk, None)
1✔
201
            if bid is not None:
1✔
202
                # hey, we got a browser id!
203
                if isAWellFormedBrowserId(bid):
1!
204
                    # bid is not "plain old broken"
205
                    REQUEST.browser_id_ = bid
1✔
206
                    REQUEST.browser_id_ns_ = name
1✔
207
                    return bid
1✔
208
        # fall through if bid is invalid or not in namespaces
209
        if create:
1✔
210
            # create a brand new bid
211
            bid = getNewBrowserId()
1✔
212
            if 'cookies' in ns:
1✔
213
                self._setCookie(bid, REQUEST)
1✔
214
            REQUEST.browser_id_ = bid
1✔
215
            REQUEST.browser_id_ns_ = None
1✔
216
            return bid
1✔
217
        # implies a return of None if:
218
        # (not create=1) and (invalid or ((not in req) and (not in ns)))
219

220
    @security.protected(access_contents_information)
1✔
221
    def getBrowserIdName(self):
1✔
222
        """ See IBrowserIdManager.
223
        """
224
        return self.browserid_name
1✔
225

226
    @security.protected(access_contents_information)
1✔
227
    def isBrowserIdNew(self):
1✔
228
        """ See IBrowserIdManager.
229
        """
230
        if not self.getBrowserId(create=False):
1✔
231
            raise BrowserIdManagerErr('There is no current browser id.')
1✔
232
        # ns will be None if new
233
        return getattr(self.REQUEST, 'browser_id_ns_', None) is None
1✔
234

235
    @security.protected(access_contents_information)
1✔
236
    def isBrowserIdFromCookie(self):
1✔
237
        """ See IBrowserIdManager.
238
        """
239
        if not self.getBrowserId(create=False):
1✔
240
            raise BrowserIdManagerErr('There is no current browser id.')
1✔
241
        if getattr(self.REQUEST, 'browser_id_ns_') == 'cookies':
1✔
242
            return 1
1✔
243

244
    @security.protected(access_contents_information)
1✔
245
    def isBrowserIdFromForm(self):
1✔
246
        """ See IBrowserIdManager.
247
        """
248
        if not self.getBrowserId(create=False):
1✔
249
            raise BrowserIdManagerErr('There is no current browser id.')
1✔
250
        if getattr(self.REQUEST, 'browser_id_ns_') == 'form':
1✔
251
            return 1
1✔
252

253
    @security.protected(access_contents_information)
1✔
254
    def isBrowserIdFromUrl(self):
1✔
255
        """ See IBrowserIdManager.
256
        """
257
        if not self.getBrowserId(create=False):
1✔
258
            raise BrowserIdManagerErr('There is no current browser id.')
1✔
259
        if getattr(self.REQUEST, 'browser_id_ns_') == 'url':
1✔
260
            return 1
1✔
261

262
    @security.protected(access_contents_information)
1✔
263
    def flushBrowserIdCookie(self):
1✔
264
        """ See IBrowserIdManager.
265
        """
266
        if 'cookies' not in self.browserid_namespaces:
1✔
267
            raise BrowserIdManagerErr(
1✔
268
                'Cookies are not now being used as a browser id namespace, '
269
                'thus the browserid cookie cannot be flushed.')
270
        self._setCookie('deleted', self.REQUEST, remove=1)
1✔
271

272
    @security.protected(access_contents_information)
1✔
273
    def setBrowserIdCookieByForce(self, bid):
1✔
274
        """ See IBrowserIdManager.
275
        """
276
        if 'cookies' not in self.browserid_namespaces:
1✔
277
            raise BrowserIdManagerErr(
1✔
278
                'Cookies are not now being used as a browser id namespace, '
279
                'thus the browserid cookie cannot be forced.')
280
        self._setCookie(bid, self.REQUEST)
1✔
281

282
    @security.protected(access_contents_information)
1✔
283
    def getHiddenFormField(self):
1✔
284
        """ See IBrowserIdManager.
285
        """
286
        s = '<input type="hidden" name="%s" value="%s" />'
1✔
287
        return s % (self.getBrowserIdName(), self.getBrowserId())
1✔
288

289
    @security.protected(access_contents_information)
1✔
290
    def encodeUrl(self, url, style='querystring', create=1):
1✔
291
        # See IBrowserIdManager
292
        bid = self.getBrowserId(create)
1✔
293
        if bid is None:
1✔
294
            raise BrowserIdManagerErr('There is no current browser id.')
1✔
295
        name = self.getBrowserIdName()
1✔
296
        if style == 'querystring':  # encode bid in querystring
1✔
297
            if '?' in url:
1✔
298
                return f'{url}&amp;{name}={bid}'
1✔
299
            else:
300
                return f'{url}?{name}={bid}'
1✔
301
        else:  # encode bid as first two URL path segments
302
            proto, host, path, params, query, frag = urlparse(url)
1✔
303
            path = f'/{name}/{bid}{path}'
1✔
304
            return urlunparse((proto, host, path, params, query, frag))
1✔
305

306
    # Non-IBrowserIdManager accessors / mutators.
307
    @security.protected(change_browser_id_managers)
1✔
308
    def setBrowserIdName(self, k):
1✔
309
        """ Set browser id name string
310

311
        o Enforce "valid" values.
312
        """
313
        if not (isinstance(k, str) and k and not badidnamecharsin(k)):
1✔
314
            raise BrowserIdManagerErr('Bad id name string %s' %
1✔
315
                                      escape(repr(k)))
316
        self.browserid_name = k
1✔
317

318
    @security.protected(change_browser_id_managers)
1✔
319
    def setBrowserIdNamespaces(self, ns):
1✔
320
        """
321
        accepts list of allowable browser id namespaces
322
        """
323
        for name in ns:
1✔
324
            if name not in ALLOWED_BID_NAMESPACES:
1✔
325
                raise BrowserIdManagerErr('Bad browser id namespace %s' %
1✔
326
                                          repr(name))
327
        self.browserid_namespaces = tuple(ns)
1✔
328

329
    @security.protected(access_contents_information)
1✔
330
    def getBrowserIdNamespaces(self):
1✔
331
        """ """
332
        return self.browserid_namespaces
1✔
333

334
    @security.protected(change_browser_id_managers)
1✔
335
    def setCookiePath(self, path=''):
1✔
336
        """ sets cookie 'path' element for id cookie """
337
        if not (isinstance(path, str) and not badcookiecharsin(path)):
1✔
338
            raise BrowserIdManagerErr('Bad cookie path %s' %
1✔
339
                                      escape(repr(path)))
340
        self.cookie_path = path
1✔
341

342
    @security.protected(access_contents_information)
1✔
343
    def getCookiePath(self):
1✔
344
        """ """
345
        return self.cookie_path
1✔
346

347
    @security.protected(change_browser_id_managers)
1✔
348
    def setCookieLifeDays(self, days):
1✔
349
        """ offset for id cookie 'expires' element """
350
        if not isinstance(days, (int, float)):
1✔
351
            raise BrowserIdManagerErr('Bad cookie lifetime in days %s '
1✔
352
                                      '(requires integer value)' %
353
                                      escape(repr(days)))
354
        self.cookie_life_days = int(days)
1✔
355

356
    @security.protected(access_contents_information)
1✔
357
    def getCookieLifeDays(self):
1✔
358
        """ """
359
        return self.cookie_life_days
1✔
360

361
    @security.protected(change_browser_id_managers)
1✔
362
    def setCookieDomain(self, domain):
1✔
363
        """ sets cookie 'domain' element for id cookie """
364
        if not isinstance(domain, str):
1✔
365
            raise BrowserIdManagerErr('Cookie domain must be string: %s' %
1✔
366
                                      escape(repr(domain)))
367
        if not domain:
1✔
368
            self.cookie_domain = ''
1✔
369
            return
1✔
370
        if not twodotsin(domain):
1✔
371
            raise BrowserIdManagerErr(
1✔
372
                'Cookie domain must contain at least two dots '
373
                '(e.g. ".zope.org" or "www.zope.org") or it must '
374
                'be left blank. : '
375
                '%s' % escape(repr(domain)))
376
        if badcookiecharsin(domain):
1✔
377
            raise BrowserIdManagerErr('Bad characters in cookie domain %s' %
1✔
378
                                      escape(repr(domain)))
379
        self.cookie_domain = domain
1✔
380

381
    @security.protected(access_contents_information)
1✔
382
    def getCookieDomain(self):
1✔
383
        """ """
384
        return self.cookie_domain
1✔
385

386
    @security.protected(change_browser_id_managers)
1✔
387
    def setCookieHTTPOnly(self, http_only):
1✔
388
        """ sets cookie 'HTTPOnly' on or off """
389
        self.cookie_http_only = bool(http_only)
1✔
390

391
    @security.protected(access_contents_information)
1✔
392
    def getCookieHTTPOnly(self):
1✔
393
        """ retrieve the 'HTTPOnly' flag """
394
        return self.cookie_http_only
1✔
395

396
    @security.protected(change_browser_id_managers)
1✔
397
    def setCookieSecure(self, secure):
1✔
398
        """ sets cookie 'secure' element for id cookie """
399
        self.cookie_secure = not not secure
1✔
400

401
    @security.protected(access_contents_information)
1✔
402
    def getCookieSecure(self):
1✔
403
        """ """
404
        return self.cookie_secure
1✔
405

406
    @security.protected(change_browser_id_managers)
1✔
407
    def setCookieSameSite(self, same_site='Lax'):
1✔
408
        """ sets cookie 'SameSite' flag """
409

410
        # Retain a way to not set the cookie at all if the admin says so
411
        if not same_site:
1✔
412
            self.cookie_same_site = None
1✔
413
            return
1✔
414

415
        if same_site.lower() not in ('none', 'lax', 'strict'):
1✔
416
            raise BrowserIdManagerErr(
1✔
417
                'Invalid value for SameSite flag, must be one of '
418
                'None, Lax or Strict.')
419

420
        # Browsers reject cookies that use SameSite=None without "Secure" flag
421
        # Make sure users don't shoot themselves in the foot.
422
        if same_site.lower() == 'none' and not self.getCookieSecure():
1✔
423
            raise BrowserIdManagerErr(
1✔
424
                'Browsers require the "Secure" flag when setting '
425
                'SameSite to "None".')
426

427
        self.cookie_same_site = same_site
1✔
428

429
    @security.protected(access_contents_information)
1✔
430
    def getCookieSameSite(self):
1✔
431
        """ retrieve the cookie 'SameSite' flag value """
432
        return self.cookie_same_site
1✔
433

434
    @security.protected(change_browser_id_managers)
1✔
435
    def setAutoUrlEncoding(self, auto_url_encoding):
1✔
436
        """ sets 'auto url encoding' on or off """
437
        self.auto_url_encoding = not not auto_url_encoding
1✔
438

439
    @security.protected(access_contents_information)
1✔
440
    def getAutoUrlEncoding(self):
1✔
441
        """ """
442
        return self.auto_url_encoding
1✔
443

444
    @security.protected(access_contents_information)
1✔
445
    def isUrlInBidNamespaces(self):
1✔
446
        """ Returns true if 'url' is in the browser id namespaces
447
        for this browser id """
448
        return 'url' in self.browserid_namespaces
1✔
449

450
    def _setCookie(self,
1✔
451
                   bid,
452
                   REQUEST,
453
                   remove=0,
454
                   now=time.time,
455
                   strftime=time.strftime,
456
                   gmtime=time.gmtime):
457
        """ """
458
        expires = None
1✔
459
        if remove:
1✔
460
            expires = "Sun, 10-May-1971 11:59:00 GMT"
1✔
461
        elif self.cookie_life_days:
1✔
462
            expires = now() + self.cookie_life_days * 86400
1✔
463
            # Wdy, DD-Mon-YYYY HH:MM:SS GMT
464
            expires = strftime('%a %d-%b-%Y %H:%M:%S GMT', gmtime(expires))
1✔
465

466
        # cookie attributes managed by BrowserIdManager
467
        d = {
1✔
468
            'domain': self.cookie_domain,
469
            'path': self.cookie_path,
470
            'secure': self.cookie_secure,
471
            'HttpOnly': self.cookie_http_only,
472
            'expires': expires,
473
            'SameSite': self.cookie_same_site,
474
        }
475

476
        if self.cookie_secure:
1✔
477
            URL1 = REQUEST.get('URL1', None)
1✔
478
            if URL1 is None:
1✔
479
                return  # should we raise an exception?
1✔
480
            if URL1.split(':')[0] != 'https':
1✔
481
                return  # should we raise an exception?
1✔
482

483
        cookies = REQUEST.RESPONSE.cookies
1✔
484
        cookie = cookies[self.browserid_name] = {}
1✔
485
        for k, v in d.items():
1✔
486
            if v:
1✔
487
                cookie[k] = v  # only stuff things with true values
1✔
488
        cookie['value'] = bid
1✔
489

490
    def _setId(self, id):
1✔
491
        if id != self.id:
1✔
492
            raise ValueError('Cannot rename a browser id manager')
1✔
493

494
    # Jukes for handling URI-munged browser IDS
495
    @security.private
1✔
496
    def hasTraversalHook(self, parent):
1✔
497
        name = TRAVERSAL_APPHANDLE
1✔
498
        return not not queryBeforeTraverse(parent, name)
1✔
499

500
    @security.private
1✔
501
    def updateTraversalData(self):
1✔
502
        if 'url' in self.browserid_namespaces:
1✔
503
            self.registerTraversalHook()
1✔
504
        else:
505
            self.unregisterTraversalHook()
1✔
506

507
    @security.private
1✔
508
    def unregisterTraversalHook(self):
1✔
509
        parent = aq_parent(aq_inner(self))
1✔
510
        name = TRAVERSAL_APPHANDLE
1✔
511
        if self.hasTraversalHook(parent):
1✔
512
            unregisterBeforeTraverse(parent, name)
1✔
513

514
    @security.private
1✔
515
    def registerTraversalHook(self):
1✔
516
        parent = aq_parent(aq_inner(self))
1✔
517
        if not self.hasTraversalHook(parent):
1✔
518
            hook = BrowserIdManagerTraverser()
1✔
519
            name = TRAVERSAL_APPHANDLE
1✔
520
            priority = 40  # "higher" priority than session data traverser
1✔
521
            registerBeforeTraverse(parent, hook, name, priority)
1✔
522

523
    # ZMI
524
    manage_options = (
1✔
525
        {
526
            'label': 'Settings',
527
            'action': 'manage_browseridmgr',
528
        },
529
        {
530
            'label': 'Security',
531
            'action': 'manage_access',
532
        },
533
        {
534
            'label': 'Ownership',
535
            'action': 'manage_owner',
536
        },
537
    )
538

539
    def manage_afterAdd(self, item, container):
1✔
540
        """ Maybe add our traversal hook """
541
        self.updateTraversalData()
1✔
542

543
    def manage_beforeDelete(self, item, container):
1✔
544
        """ Remove our traversal hook if it exists """
545
        self.unregisterTraversalHook()
×
546

547
    security.declareProtected(
1✔
548
        view_management_screens,  # noqa: D001
549
        'manage_browseridmgr')
550
    manage_browseridmgr = DTMLFile('dtml/manageIdManager', globals())
1✔
551

552
    @security.protected(change_browser_id_managers)
1✔
553
    def manage_changeBrowserIdManager(self,
1✔
554
                                      title='',
555
                                      idname='_ZopeId',
556
                                      location=('cookies', 'form'),
557
                                      cookiepath='/',
558
                                      cookiedomain='',
559
                                      cookielifedays=0,
560
                                      cookiesecure=0,
561
                                      cookiehttponly=0,
562
                                      auto_url_encoding=0,
563
                                      cookiesamesite=None,
564
                                      REQUEST=None):
565
        """ """
566
        self.title = str(title)
×
567
        self.setBrowserIdName(idname)
×
568
        self.setCookiePath(cookiepath)
×
569
        self.setCookieDomain(cookiedomain)
×
570
        self.setCookieLifeDays(cookielifedays)
×
571
        self.setCookieSecure(cookiesecure)
×
572
        self.setCookieHTTPOnly(cookiehttponly)
×
573
        self.setBrowserIdNamespaces(location)
×
574
        self.setAutoUrlEncoding(auto_url_encoding)
×
575
        self.setCookieSameSite(cookiesamesite)
×
576
        self.updateTraversalData()
×
577
        if REQUEST is not None:
×
578
            msg = '/manage_browseridmgr?manage_tabs_message=Changes saved'
×
579
            REQUEST.RESPONSE.redirect(self.absolute_url() + msg)
×
580

581

582
InitializeClass(BrowserIdManager)
1✔
583

584

585
class BrowserIdManagerTraverser(Persistent):
1✔
586

587
    def __call__(self,
1✔
588
                 container,
589
                 request,
590
                 browser_id=None,
591
                 browser_id_ns=None,
592
                 BROWSERID_MANAGER_NAME=BROWSERID_MANAGER_NAME):
593
        """
594
        Registered hook to set and get a browser id in the URL.  If
595
        a browser id is found in the URL of an  incoming request, we put it
596
        into a place where it can be found later by the browser id manager.
597
        If our browser id manager's auto-url-encoding feature is on, cause
598
        Zope-generated URLs to contain the browser id by rewriting the
599
        request._script list.
600
        """
601
        browser_id_manager = getattr(container, BROWSERID_MANAGER_NAME, None)
1✔
602
        # fail if we cannot find a browser id manager (that means this
603
        # instance has been "orphaned" somehow)
604
        if browser_id_manager is None:
1✔
605
            LOG.error('Could not locate browser id manager!')
1✔
606
            return
1✔
607

608
        try:
1✔
609
            stack = request['TraversalRequestNameStack']
1✔
610
            request.browser_id_ns_ = browser_id_ns
1✔
611
            bid_name = browser_id_manager.getBrowserIdName()
1✔
612

613
            # stuff the browser id and id namespace into the request
614
            # if the URL has a browser id name and browser id as its first
615
            # two elements.  Only remove these elements from the
616
            # traversal stack if they are a "well-formed pair".
617
            if len(stack) >= 2 and stack[-1] == bid_name:
1✔
618
                if isAWellFormedBrowserId(stack[-2]):
1!
619
                    stack.pop()  # pop the name off the stack
1✔
620
                    browser_id = stack.pop()  # pop id off the stack
1✔
621
                    request.browser_id_ = browser_id
1✔
622
                    request.browser_id_ns_ = 'url'
1✔
623

624
            # if the browser id manager is set up with 'auto url encoding',
625
            # cause generated URLs to be encoded with the browser id name/value
626
            # pair by munging request._script.
627
            if browser_id_manager.getAutoUrlEncoding():
1✔
628
                if browser_id is None:
1✔
629
                    request.browser_id_ = browser_id = getNewBrowserId()
1✔
630
                request._script.append(quote(bid_name))
1✔
631
                request._script.append(quote(browser_id))
1✔
632
        except Exception:
1✔
633
            LOG.error('indeterminate error', exc_info=sys.exc_info())
1✔
634

635

636
def getB64TStamp(
1✔
637
    b2a=binascii.b2a_base64,
638
    gmtime=time.gmtime,
639
    time=time.time,
640
    TimeStamp=TimeStamp.TimeStamp,
641
):
642
    t = time()
1✔
643
    stamp = TimeStamp(*gmtime(t)[:5] + (t % 60, ))
1✔
644
    ts = b2a(stamp.raw()).split(b'=')[:-1][0]
1✔
645
    return ts.replace(b'/', b'.').replace(b'+', b'-').decode('ascii')
1✔
646

647

648
def getB64TStampToInt(ts,
1✔
649
                      TimeStamp=TimeStamp.TimeStamp,
650
                      a2b=binascii.a2b_base64):
651
    stamp = TimeStamp(a2b(ts.replace('.', '/').replace('-', '+') + '='))
1✔
652
    return stamp.timeTime()
1✔
653

654

655
def getBrowserIdPieces(bid):
1✔
656
    """returns browser id parts in a tuple consisting of rand_id,
657
    timestamp
658
    """
659
    return (bid[:8], bid[8:19])
1✔
660

661

662
def isAWellFormedBrowserId(bid, binerr=binascii.Error):
1✔
663
    try:
1✔
664
        rnd, ts = getBrowserIdPieces(bid)
1✔
665
        int(rnd)
1✔
666
        getB64TStampToInt(ts)
1✔
667
        return bid
1✔
668
    except (TypeError, ValueError, AttributeError, IndexError, binerr):
1✔
669
        return None
1✔
670

671

672
def getNewBrowserId(randint=_randint, maxint=99999999):
1✔
673
    """Returns 19-character string browser id
674
    'AAAAAAAABBBBBBBB'
675
    where:
676

677
    A == leading-0-padded 8-char string-rep'd random integer
678
    B == modified base64-encoded 11-char timestamp
679

680
    To be URL-compatible, base64 encoding is modified as follows:
681
      '=' end-padding is stripped off
682
      '+' is translated to '-'
683
      '/' is translated to '.'
684

685
    An example is: 89972317A0C3EHnUi90w
686
    """
687
    return '%08i%s' % (randint(0, maxint - 1), getB64TStamp())
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