1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
from inspect import getmembers, ismethod, isfunction
from webob import exc
from decorators import expose
from util import _cfg, iscontroller
__all__ = ['unlocked', 'secure', 'SecureController']
class _SecureState(object):
def __init__(self, desc, boolean_value):
self.description = desc
self.boolean_value = boolean_value
def __repr__(self):
return '<SecureState %s>' % self.description
def __nonzero__(self):
return self.boolean_value
Any = _SecureState('Any', False)
Protected = _SecureState('Protected', True)
# security method decorators
def _unlocked_method(func):
_cfg(func)['secured'] = Any
return func
def _secure_method(check_permissions_func):
def wrap(func):
cfg = _cfg(func)
cfg['secured'] = Protected
cfg['check_permissions'] = check_permissions_func
return func
return wrap
# classes to assist with wrapping attributes
class _UnlockedAttribute(object):
def __init__(self, obj):
self.obj = obj
@_unlocked_method
@expose()
def _lookup(self, *remainder):
return self.obj, remainder
class _SecuredAttribute(object):
def __init__(self, obj, check_permissions):
self.obj = obj
self.check_permissions = check_permissions
self._parent = None
def _check_permissions(self):
if isinstance(self.check_permissions, basestring):
return getattr(self.parent, self.check_permissions)()
else:
return self.check_permissions()
def __get_parent(self):
return self._parent
def __set_parent(self, parent):
if ismethod(parent):
self._parent = parent.im_self
else:
self._parent = parent
parent = property(__get_parent, __set_parent)
@_secure_method('_check_permissions')
@expose()
def _lookup(self, *remainder):
return self.obj, remainder
# helper for secure decorator
def _allowed_check_permissions_types(x):
return (ismethod(x) or
isfunction(x) or
isinstance(x, basestring)
)
# methods that can either decorate functions or wrap classes
# these should be the main methods used for securing or unlocking
def unlocked(func_or_obj):
"""
This method unlocks method or class attribute on a SecureController. Can
be used to decorate or wrap an attribute
"""
if ismethod(func_or_obj) or isfunction(func_or_obj):
return _unlocked_method(func_or_obj)
else:
return _UnlockedAttribute(func_or_obj)
def secure(func_or_obj, check_permissions_for_obj=None):
"""
This method secures a method or class depending on invocation.
To decorate a method use one argument:
@secure(<check_permissions_method>)
To secure a class, invoke with two arguments:
secure(<obj instance>, <check_permissions_method>)
"""
if _allowed_check_permissions_types(func_or_obj):
return _secure_method(func_or_obj)
else:
if not _allowed_check_permissions_types(check_permissions_for_obj):
msg = "When securing an object, secure() requires the " + \
"second argument to be method"
raise TypeError(msg)
return _SecuredAttribute(func_or_obj, check_permissions_for_obj)
class SecureController(object):
"""
Used to apply security to a controller.
Implementations of SecureController should extend the
`check_permissions` method to return a True or False
value (depending on whether or not the user has permissions
to the controller).
"""
class __metaclass__(type):
def __init__(cls, name, bases, dict_):
cls._pecan = dict(
secured=Protected,
check_permissions=cls.check_permissions,
unlocked=[]
)
for name, value in getmembers(cls):
if ismethod(value):
if iscontroller(value) and value._pecan.get(
'secured'
) is None:
value._pecan['secured'] = Protected
value._pecan['check_permissions'] = \
cls.check_permissions
elif hasattr(value, '__class__'):
if name.startswith('__') and name.endswith('__'):
continue
if isinstance(value, _UnlockedAttribute):
# mark it as unlocked and remove wrapper
cls._pecan['unlocked'].append(value.obj)
setattr(cls, name, value.obj)
elif isinstance(value, _SecuredAttribute):
# The user has specified a different check_permissions
# than the class level version. As far as the class
# is concerned, this method is unlocked because
# it is using a check_permissions function embedded in
# the _SecuredAttribute wrapper
cls._pecan['unlocked'].append(value)
@classmethod
def check_permissions(cls):
return False
# methods to evaluate security during routing
def handle_security(controller):
""" Checks the security of a controller. """
if controller._pecan.get('secured', False):
check_permissions = controller._pecan['check_permissions']
if isinstance(check_permissions, basestring):
check_permissions = getattr(controller.im_self, check_permissions)
if not check_permissions():
raise exc.HTTPUnauthorized
def cross_boundary(prev_obj, obj):
""" Check permissions as we move between object instances. """
if prev_obj is None:
return
if isinstance(obj, _SecuredAttribute):
# a secure attribute can live in unsecure class so we have to set
# while we walk the route
obj.parent = prev_obj
if hasattr(prev_obj, '_pecan'):
if obj not in prev_obj._pecan.get('unlocked', []):
handle_security(prev_obj)
|