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

zopefoundation / AccessControl / 3742027669

pending completion
3742027669

push

github

Michael Howitz
Drop support for Python 2.7 up to 3.6.

934 of 1458 branches covered (64.06%)

Branch coverage included in aggregate %.

100 of 105 new or added lines in 26 files covered. (95.24%)

4982 of 5839 relevant lines covered (85.32%)

4.26 hits per line

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

84.04
/src/AccessControl/users.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
"""Access control package.
5✔
14
"""
15

16

17
import os
5✔
18
import re
5✔
19
import socket
5✔
20

21
from Acquisition import Implicit
5✔
22
from Acquisition import aq_inContextOf
5✔
23
from AuthEncoding import AuthEncoding
5✔
24
from Persistence import Persistent
5✔
25
from zope.interface import implementer
5✔
26

27
from AccessControl import SpecialUsers
5✔
28
from AccessControl.interfaces import IUser
5✔
29
from AccessControl.PermissionRole import _what_not_even_god_should_do
5✔
30
from AccessControl.PermissionRole import rolesForPermissionOn
5✔
31

32

33
_marker = []
5✔
34

35

36
@implementer(IUser)
5✔
37
class BasicUser(Implicit):
5✔
38

39
    """Base class for all User objects"""
40
    zmi_icon = 'fa fa-user'
5✔
41

42
    # ----------------------------
43
    # Public User object interface
44
    # ----------------------------
45

46
    # Maybe allow access to unprotected attributes. Note that this is
47
    # temporary to avoid exposing information but without breaking
48
    # everyone's current code. In the future the security will be
49
    # clamped down and permission-protected here. Because there are a
50
    # fair number of user object types out there, this method denies
51
    # access to names that are private parts of the standard User
52
    # interface or implementation only. The other approach (only
53
    # allowing access to public names in the User interface) would
54
    # probably break a lot of other User implementations with extended
55
    # functionality that we cant anticipate from the base scaffolding.
56
    def __allow_access_to_unprotected_subobjects__(self, name, value=None):
5✔
57
        deny_names = (
×
58
            'name',
59
            '__',
60
            'roles',
61
            'domains',
62
            '_getPassword',
63
            'authenticate',
64
        )
65
        if name in deny_names:
×
66
            return 0
×
67
        return 1
×
68

69
    def __init__(self, name, password, roles, domains):
5✔
70
        raise NotImplementedError
71

72
    def getId(self):
5✔
73
        """Get the ID of the user.
74
        """
75
        return self.getUserName()
5✔
76

77
    def getUserName(self):
5✔
78
        """Get the name used by the user to log into the system.
79
        """
80
        raise NotImplementedError
81

82
    def getRoles(self):
5✔
83
        """Get a sequence of the global roles assigned to the user.
84
        """
85
        raise NotImplementedError
86

87
    def getRolesInContext(self, object):
5✔
88
        """Get a sequence of the roles assigned to the user in a context.
89
        """
90
        userid = self.getId()
5✔
91
        roles = self.getRoles()
5✔
92
        local = {}
5✔
93
        object = getattr(object, 'aq_inner', object)
5✔
94
        while True:
3✔
95
            local_roles = getattr(object, '__ac_local_roles__', None)
5✔
96
            if local_roles:
5✔
97
                if callable(local_roles):
5✔
98
                    local_roles = local_roles()
5✔
99
                dict = local_roles or {}
5✔
100
                for r in dict.get(userid, []):
5✔
101
                    local[r] = 1
5✔
102
            inner = getattr(object, 'aq_inner', object)
5✔
103
            parent = getattr(inner, '__parent__', None)
5✔
104
            if parent is not None:
5✔
105
                object = parent
5✔
106
                continue
5✔
107
            if getattr(object, '__self__', None) is not None:
5✔
108
                object = object.__self__
5✔
109
                object = getattr(object, 'aq_inner', object)
5✔
110
                continue
5✔
111
            break
3✔
112
        roles = list(roles) + list(local.keys())
5✔
113
        return roles
5✔
114

115
    def getDomains(self):
5✔
116
        """Get a sequence of the domain restrictions for the user.
117
        """
118
        raise NotImplementedError
119

120
    # ------------------------------
121
    # Internal User object interface
122
    # ------------------------------
123

124
    def _getPassword(self):
5✔
125
        """Return the password of the user.
126
        """
127
        raise NotImplementedError
128

129
    def authenticate(self, password, request):
5✔
130
        passwrd = self._getPassword()
5✔
131
        result = AuthEncoding.pw_validate(passwrd, password)
5✔
132
        domains = self.getDomains()
5✔
133
        if domains:
5✔
134
            return result and domainSpecMatch(domains, request)
5✔
135
        return result
5✔
136

137
    def _check_context(self, object):
5✔
138
        # Check that 'object' exists in the acquisition context of
139
        # the parent of the acl_users object containing this user,
140
        # to prevent "stealing" access through acquisition tricks.
141
        # Return true if in context, false if not or if context
142
        # cannot be determined (object is not wrapped).
143
        parent = getattr(self, '__parent__', None)
5✔
144
        context = getattr(parent, '__parent__', None)
5✔
145
        if context is not None:
5✔
146
            if object is None:
5!
147
                return 1
×
148
            if getattr(object, '__self__', None) is not None:
5!
149
                # This is a method.  Grab its self.
150
                object = object.__self__
×
151
            return aq_inContextOf(object, context, 1)
5✔
152

153
        # This is lame, but required to keep existing behavior.
154
        return 1
5✔
155

156
    def allowed(self, object, object_roles=None):
5✔
157
        """Check whether the user has access to object. The user must
158
           have one of the roles in object_roles to allow access."""
159

160
        if object_roles is _what_not_even_god_should_do:
5✔
161
            return 0
5✔
162

163
        # Short-circuit the common case of anonymous access.
164
        if object_roles is None or 'Anonymous' in object_roles:
5✔
165
            return 1
5✔
166

167
        # Provide short-cut access if object is protected by 'Authenticated'
168
        # role and user is not nobody
169
        if 'Authenticated' in object_roles and \
5✔
170
                self.getUserName() != 'Anonymous User':
171
            if self._check_context(object):
5✔
172
                return 1
5✔
173

174
        # Check for a role match with the normal roles given to
175
        # the user, then with local roles only if necessary. We
176
        # want to avoid as much overhead as possible.
177
        user_roles = self.getRoles()
5✔
178
        for role in object_roles:
5✔
179
            if role in user_roles:
5✔
180
                if self._check_context(object):
5✔
181
                    return 1
5✔
182
                return None
5✔
183

184
        # Still have not found a match, so check local roles. We do
185
        # this manually rather than call getRolesInContext so that
186
        # we can incur only the overhead required to find a match.
187
        inner_obj = getattr(object, 'aq_inner', object)
5✔
188
        userid = self.getId()
5✔
189
        parents = set()
5✔
190
        while True:
3✔
191
            local_roles = getattr(inner_obj, '__ac_local_roles__', None)
5✔
192
            if local_roles:
5✔
193
                if callable(local_roles):
5!
194
                    local_roles = local_roles()
×
195
                dict = local_roles or {}
5✔
196
                local_roles = dict.get(userid, [])
5✔
197
                for role in object_roles:
5✔
198
                    if role in local_roles:
5✔
199
                        if self._check_context(object):
5✔
200
                            return 1
5✔
201
                        return 0
5✔
202
            inner = getattr(inner_obj, 'aq_inner', inner_obj)
5✔
203
            parent = getattr(inner, '__parent__', None)
5✔
204
            if parent is not None:
5✔
205
                if parent in parents:
5!
206
                    break
×
207
                parents.add(parent)
5✔
208
                inner_obj = parent
5✔
209
                continue
5✔
210
            if getattr(inner_obj, '__self__', None) is not None:
5✔
211
                inner_obj = inner_obj.__self__
5✔
212
                inner_obj = getattr(inner_obj, 'aq_inner', inner_obj)
5✔
213
                continue
5✔
214
            break
3✔
215
        return None
5✔
216

217
    domains = []
5✔
218

219
    def has_role(self, roles, object=None):
5✔
220
        """Check if the user has at least one role from a list of roles.
221

222
        If object is specified, check in the context of the passed in object.
223
        """
224
        if isinstance(roles, str):
5✔
225
            roles = [roles]
5✔
226
        if object is not None:
5✔
227
            user_roles = self.getRolesInContext(object)
5✔
228
        else:
229
            # Global roles only...
230
            user_roles = self.getRoles()
5✔
231
        for role in roles:
5✔
232
            if role in user_roles:
5✔
233
                return 1
5✔
234
        return 0
5✔
235

236
    def has_permission(self, permission, object):
5✔
237
        """Check if the user has a permission on an object.
238

239
        This method is just for inspecting permission settings. For access
240
        control use getSecurityManager().checkPermission() instead.
241
        """
242
        roles = rolesForPermissionOn(permission, object)
5✔
243
        if isinstance(roles, str):
5!
244
            roles = [roles]
×
245
        return self.allowed(object, roles)
5✔
246

247
    def __len__(self):
5✔
248
        return 1
5✔
249

250
    def __str__(self):
5✔
251
        return self.getUserName()
5✔
252

253
    def __repr__(self):
5✔
254
        return '<{} {!r}>'.format(self.__class__.__name__, self.getUserName())
5✔
255

256

257
class SimpleUser(BasicUser):
5✔
258
    """A very simple user implementation
259

260
    that doesn't make a database commitment"""
261

262
    def __init__(self, name, password, roles, domains):
5✔
263
        self.name = name
5✔
264
        self.__ = password
5✔
265
        self.roles = roles
5✔
266
        self.domains = domains
5✔
267

268
    def getUserName(self):
5✔
269
        """Get the name used by the user to log into the system.
270
        """
271
        return self.name
5✔
272

273
    def getRoles(self):
5✔
274
        """Get a sequence of the global roles assigned to the user.
275
        """
276
        if self.name == 'Anonymous User':
5✔
277
            return tuple(self.roles)
5✔
278
        else:
279
            return tuple(self.roles) + ('Authenticated', )
5✔
280

281
    def getDomains(self):
5✔
282
        """Get a sequence of the domain restrictions for the user.
283
        """
284
        return tuple(self.domains)
5✔
285

286
    def _getPassword(self):
5✔
287
        """Return the password of the user.
288
        """
289
        return self.__
5✔
290

291

292
class SpecialUser(SimpleUser):
5✔
293
    """Class for special users, like emergency user and nobody"""
294

295
    def getId(self):
5✔
296
        pass
5✔
297

298

299
class User(SimpleUser, Persistent):
5✔
300
    """Standard User object"""
301

302

303
class UnrestrictedUser(SpecialUser):
5✔
304
    """User that passes all security checks.  Note, however, that modules
305
    like Owner.py can still impose restrictions.
306
    """
307

308
    def allowed(self, parent, roles=None):
5✔
309
        return roles is not _what_not_even_god_should_do
5✔
310

311
    def has_role(self, roles, object=None):
5✔
312
        return 1
5✔
313

314
    def has_permission(self, permission, object):
5✔
315
        return 1
×
316

317

318
class NullUnrestrictedUser(SpecialUser):
5✔
319
    """User created if no emergency user exists. It is only around to
320
       satisfy third party userfolder implementations that may
321
       expect the emergency user to exist and to be able to call certain
322
       methods on it (in other words, backward compatibility).
323

324
       Note that when no emergency user is installed, this object that
325
       exists in its place is more of an anti-superuser since you cannot
326
       login as this user and it has no privileges at all."""
327

328
    __null_user__ = 1
5✔
329

330
    def __init__(self):
5✔
331
        pass
5✔
332

333
    def getUserName(self):
5✔
334
        # return an unspellable username
335
        return (None, None)
5✔
336
    _getPassword = getUserName
5✔
337

338
    def getRoles(self):
5✔
339
        return ()
5✔
340
    getDomains = getRoles
5✔
341

342
    def getRolesInContext(self, object):
5✔
343
        return ()
5✔
344

345
    def authenticate(self, password, request):
5✔
346
        return 0
5✔
347

348
    def allowed(self, parent, roles=None):
5✔
349
        return 0
5✔
350

351
    def has_role(self, roles, object=None):
5✔
352
        return 0
5✔
353

354
    def has_permission(self, permission, object):
5✔
355
        return 0
5✔
356

357
    def __str__(self):
5✔
358
        return repr(self)
5✔
359

360

361
def readUserAccessFile(filename):
5✔
362
    '''Reads an access file from the instance home.
363
    Returns name, password, domains, remote_user_mode.
364
    '''
365
    environ = os.environ
5✔
366
    instancehome = environ.get('INSTANCE_HOME', None)
5✔
367
    if not instancehome:
5!
368
        return None
5✔
369

370
    try:
×
371
        with open(os.path.join(instancehome, filename), 'rb') as f:
×
372
            line = f.readline()
×
NEW
373
    except OSError:
×
374
        return None
×
375

376
    if line:
×
377
        data = line.strip().split(b':')
×
378
        user = data[0].decode('utf-8')
×
379
        remote_user_mode = not data[1]
×
380
        try:
×
381
            ds = data[2].split(b' ')
×
382
        except IndexError:
×
383
            ds = []
×
384
        return (user, data[1], ds, remote_user_mode)
×
385
    else:
386
        return None
×
387

388

389
# Create emergency user.
390
_remote_user_mode = 0
5✔
391

392
info = readUserAccessFile('access')
5✔
393
if info:
5!
394
    _remote_user_mode = info[3]
×
395
    emergency_user = UnrestrictedUser(info[0], info[1], ('manage', ), info[2])
×
396
else:
397
    emergency_user = NullUnrestrictedUser()
5✔
398

399
del info
5✔
400

401

402
nobody = SpecialUser('Anonymous User', '', ('Anonymous', ), [])
5✔
403
system = UnrestrictedUser('System Processes', '', ('manage', ), [])
5✔
404

405
# stuff these in a handier place for importing
406
SpecialUsers.nobody = nobody
5✔
407
SpecialUsers.system = system
5✔
408
SpecialUsers.emergency_user = emergency_user
5✔
409
# Note: use of the 'super' name is deprecated.
410
SpecialUsers.super = emergency_user
5✔
411

412

413
def rolejoin(roles, other):
5✔
414
    return sorted(set(roles).union(other))
×
415

416

417
addr_match = re.compile(r'((\d{1,3}\.){1,3}\*)|((\d{1,3}\.){3}\d{1,3})').match
5✔
418
host_match = re.compile(r'(([\_0-9a-zA-Z\-]*\.)*[0-9a-zA-Z\-]*)').match
5✔
419

420

421
def domainSpecMatch(spec, request):
5✔
422
    # Fast exit for the match-all case
423
    if len(spec) == 1 and spec[0] == '*':
5✔
424
        return 1
5✔
425

426
    host = request.get('REMOTE_HOST', '')
5✔
427
    addr = request.getClientAddr()
5✔
428

429
    if not host and not addr:
5✔
430
        return 0
5✔
431

432
    if not host:
5✔
433
        try:
5✔
434
            host = socket.gethostbyaddr(addr)[0]
5✔
435
        except Exception:
×
436
            pass
×
437

438
    if not addr:
5✔
439
        try:
5✔
440
            addr = socket.gethostbyname(host)
5✔
441
        except Exception:
5✔
442
            # always define localhost, even if the underlying system
443
            # doesn't know about it, this fixes tests on travis
444
            if host == 'localhost':
5!
445
                addr = '127.0.0.1'
×
446

447
    _host = host.split('.')
5✔
448
    _addr = addr.split('.')
5✔
449
    _hlen = len(_host)
5✔
450

451
    for ob in spec:
5✔
452
        sz = len(ob)
5✔
453
        _ob = ob.split('.')
5✔
454
        _sz = len(_ob)
5✔
455

456
        mo = addr_match(ob)
5✔
457
        if mo is not None:
5✔
458
            if mo.end(0) == sz:
5!
459
                fail = 0
5✔
460
                for i in range(_sz):
5✔
461
                    a = _addr[i]
5✔
462
                    o = _ob[i]
5✔
463
                    if (o != a) and (o != '*'):
5✔
464
                        fail = 1
5✔
465
                        break
5✔
466
                if fail:
5✔
467
                    continue
5✔
468
                return 1
5✔
469

470
        mo = host_match(ob)
5✔
471
        if mo is not None:
5!
472
            if mo.end(0) == sz:
5!
473
                if _hlen < _sz:
5!
474
                    continue
×
475
                elif _hlen > _sz:
5✔
476
                    _item = _host[-_sz:]
5✔
477
                else:
478
                    _item = _host
5✔
479
                fail = 0
5✔
480
                for i in range(_sz):
5✔
481
                    h = _item[i]
5✔
482
                    o = _ob[i]
5✔
483
                    if (o != h) and (o != '*'):
5!
484
                        fail = 1
×
485
                        break
×
486
                if fail:
5!
487
                    continue
×
488
                return 1
5✔
489
    return 0
5✔
490

491

492
def absattr(attr):
5✔
493
    if callable(attr):
×
494
        return attr()
×
495
    return attr
×
496

497

498
def reqattr(request, attr):
5✔
499
    try:
×
500
        return request[attr]
×
501
    except BaseException:
×
502
        return None
×
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

© 2026 Coveralls, Inc