-
Notifications
You must be signed in to change notification settings - Fork 12
/
__init__.py
329 lines (277 loc) · 13.5 KB
/
__init__.py
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Pyramid authn policy that ties together multiple backends.
"""
__ver_major__ = 0
__ver_minor__ = 4
__ver_patch__ = 0
__ver_sub__ = ""
__ver_tuple__ = (__ver_major__, __ver_minor__, __ver_patch__, __ver_sub__)
__version__ = "%d.%d.%d%s" % __ver_tuple__
import sys
from zope.interface import implementer
from pyramid.interfaces import IAuthenticationPolicy, PHASE2_CONFIG
from pyramid.security import Everyone, Authenticated
from pyramid.authorization import ACLAuthorizationPolicy
if sys.version_info > (3,): # pragma: nocover
basestring = str
class MultiAuthPolicySelected(object):
"""Event for tracking which authentication policy was used.
This event is fired whenever a particular backend policy is successfully
used for authentication. It can be used by other parts of the code in
order to act based on the selected policy::
from pyramid.events import subscriber
@subscriber(MultiAuthPolicySelected)
def track_policy(event):
print("We selected policy %s" % event.policy)
"""
def __init__(self, policy, request):
self.policy = policy
self.request = request
@implementer(IAuthenticationPolicy)
class MultiAuthenticationPolicy(object):
"""Pyramid authentication policy for stacked authentication.
This is a pyramid authentication policy that stitches together other
authentication policies into a flexible auth stack. You give it a
list of IAuthenticationPolicy objects, and it will try each one in
turn until it obtains a usable response:
* authenticated_userid: return userid from first successful policy
* unauthenticated_userid: return userid from first successful policy
* effective_principals: return union of principals from all policies
* remember: return headers from all policies
* forget: return headers from all policies
"""
def __init__(self, policies, callback=None):
self._policies = policies
self._callback = callback
def authenticated_userid(self, request):
"""Find the authenticated userid for this request.
This method delegates to each authn policy in turn, taking the
userid from the first one that doesn't return None. If a
groupfinder callback is configured, it is also used to validate
the userid before returning.
"""
userid = None
for policy in self._policies:
userid = policy.authenticated_userid(request)
if userid is not None:
request.registry.notify(MultiAuthPolicySelected(policy, request))
if self._callback is None:
break
if self._callback(userid, request) is not None:
break
else:
userid = None
return userid
def unauthenticated_userid(self, request):
"""Find the unauthenticated userid for this request.
This method delegates to each authn policy in turn, taking the
userid from the first one that doesn't return None.
"""
userid = None
for policy in self._policies:
userid = policy.unauthenticated_userid(request)
if userid is not None:
break
return userid
def effective_principals(self, request):
"""Get the list of effective principals for this request.
This method returns the union of the principals returned by each
authn policy. If a groupfinder callback is registered, its output
is also added to the list.
"""
principals = set((Everyone,))
for policy in self._policies:
principals.update(policy.effective_principals(request))
if self._callback is not None:
principals.discard(Authenticated)
groups = None
for policy in self._policies:
userid = policy.authenticated_userid(request)
if userid is None:
continue
groups = self._callback(userid, request)
if groups is not None:
break
if groups is not None:
principals.add(userid)
principals.add(Authenticated)
principals.update(groups)
return list(principals)
def remember(self, request, principal, **kw):
"""Remember the authenticated userid.
This method returns the concatentation of the headers returned by each
authn policy.
"""
headers = []
for policy in self._policies:
headers.extend(policy.remember(request, principal, **kw))
return headers
def forget(self, request):
"""Forget a previusly remembered userid.
This method returns the concatentation of the headers returned by each
authn policy.
"""
headers = []
for policy in self._policies:
headers.extend(policy.forget(request))
return headers
def get_policy(self, name_or_class):
"""Get one of the contained authentication policies, by name or class.
This method can be used to obtain one of the subpolicies loaded
by this policy object. The policy can be looked up either by the
name given to it in the config settings, or or by its class. If
no policy is found matching the given query, None is returned.
This may be useful if you need to access non-standard methods or
properties on one of the loaded policy objects.
"""
for policy in self._policies:
if isinstance(name_or_class, basestring):
policy_name = getattr(policy, "_pyramid_multiauth_name", None)
if policy_name == name_or_class:
return policy
else:
if isinstance(policy, name_or_class):
return policy
return None
def includeme(config):
"""Include pyramid_multiauth into a pyramid configurator.
This function provides a hook for pyramid to include the default settings
for auth via pyramid_multiauth. Activate it like so:
config.include("pyramid_multiauth")
This will pull the list of registered authn policies from the deployment
settings, and configure and install each policy in order. The policies to
use can be specified in one of two ways:
* as the name of a module to be included.
* as the name of a callable along with a set of parameters.
Here's an example suite of settings:
multiauth.policies = ipauth1 ipauth2 pyramid_browserid
multiauth.policy.ipauth1.use = pyramid_ipauth.IPAuthentictionPolicy
multiauth.policy.ipauth1.ipaddrs = 123.123.0.0/16
multiauth.policy.ipauth1.userid = local1
multiauth.policy.ipauth2.use = pyramid_ipauth.IPAuthentictionPolicy
multiauth.policy.ipauth2.ipaddrs = 124.124.0.0/16
multiauth.policy.ipauth2.userid = local2
This will configure a MultiAuthenticationPolicy with three policy objects.
The first two will be IPAuthenticationPolicy objects created by passing
in the specified keyword arguments. The third will be a BrowserID
authentication policy just like you would get from executing:
config.include("pyramid_browserid")
As a side-effect, the configuration will also get the additional views
that pyramid_browserid sets up by default.
"""
# Grab the pyramid-wide settings, to look for any auth config.
settings = config.get_settings()
# Hook up a default AuthorizationPolicy.
# ACLAuthorizationPolicy is usually what you want.
# If the app configures one explicitly then this will get overridden.
# In autocommit mode this needs to be done before setting the authn policy.
authz_policy = ACLAuthorizationPolicy()
config.set_authorization_policy(authz_policy)
# Get the groupfinder from config if present.
groupfinder = settings.get("multiauth.groupfinder", None)
groupfinder = config.maybe_dotted(groupfinder)
# Look for callable policy definitions.
# Suck them all out at once and store them in a dict for later use.
policy_definitions = get_policy_definitions(settings)
# Read and process the list of policies to load.
# We build up a list of callables which can be executed at config commit
# time to obtain the final list of policies.
# Yeah, it's complicated. But we want to be able to inherit any default
# views or other config added by the sub-policies when they're included.
# Process policies in reverse order so that things at the front of the
# list can override things at the back of the list.
policy_factories = []
policy_names = settings.get("multiauth.policies", "").split()
for policy_name in reversed(policy_names):
if policy_name in policy_definitions:
# It's a policy defined using a callable.
# Just append it straight to the list.
definition = policy_definitions[policy_name]
factory = config.maybe_dotted(definition.pop("use"))
policy_factories.append((factory, policy_name, definition))
else:
# It's a module to be directly included.
try:
factory = policy_factory_from_module(config, policy_name)
except ImportError:
err = "pyramid_multiauth: policy %r has no settings "\
"and is not importable" % (policy_name,)
raise ValueError(err)
policy_factories.append((factory, policy_name, {}))
# OK. We now have a list of callbacks which need to be called at
# commit time, and will return the policies in reverse order.
# Register a special action to pull them into our list of policies.
policies = []
def grab_policies(): # NOQA
for factory, name, kwds in policy_factories:
policy = factory(**kwds)
if policy:
policy._pyramid_multiauth_name = name
if not policies or policy is not policies[0]:
# Remember, they're being processed in reverse order.
# So each new policy needs to go at the front.
policies.insert(0, policy)
config.action(None, grab_policies, order=PHASE2_CONFIG)
authn_policy = MultiAuthenticationPolicy(policies, groupfinder)
config.set_authentication_policy(authn_policy)
def policy_factory_from_module(config, module):
"""Create a policy factory that works by config.include()'ing a module.
This function does some trickery with the Pyramid config system. Loosely,
it does config.include(module), and then sucks out information about the
authn policy that was registered. It's complicated by pyramid's delayed-
commit system, which means we have to do the work via callbacks.
"""
# Remember the policy that's active before including the module, if any.
orig_policy = config.registry.queryUtility(IAuthenticationPolicy)
# Include the module, so we get any default views etc.
config.include(module)
# That might have registered and commited a new policy object.
policy = config.registry.queryUtility(IAuthenticationPolicy)
if policy is not None and policy is not orig_policy:
return lambda: policy
# Or it might have set up a pending action to register one later.
# Find the most recent IAuthenticationPolicy action, and grab
# out the registering function so we can call it ourselves.
for action in reversed(config.action_state.actions):
# Extract the discriminator and callable. This is complicated by
# Pyramid 1.3 changing action from a tuple to a dict.
try:
discriminator = action["discriminator"]
callable = action["callable"]
except TypeError: # pragma: nocover
discriminator = action[0] # pragma: nocover
callable = action[1] # pragma: nocover
# If it's not setting the authn policy, keep looking.
if discriminator is not IAuthenticationPolicy:
continue
# Otherwise, wrap it up so we can extract the registered object.
def grab_policy(register=callable): # NOQA
old_policy = config.registry.queryUtility(IAuthenticationPolicy)
register()
new_policy = config.registry.queryUtility(IAuthenticationPolicy)
config.registry.registerUtility(old_policy, IAuthenticationPolicy)
return new_policy
return grab_policy
# Or it might not have done *anything*.
# So return a null policy factory.
return lambda: None
def get_policy_definitions(settings):
"""Find all multiauth policy definitions from the settings dict.
This function processes the paster deployment settings looking for items
that start with "multiauth.policy.<policyname>.". It pulls them all out
into a dict indexed by the policy name.
"""
policy_definitions = {}
for name in settings:
if not name.startswith("multiauth.policy."):
continue
value = settings[name]
name = name[len("multiauth.policy."):]
policy_name, setting_name = name.split(".", 1)
if policy_name not in policy_definitions:
policy_definitions[policy_name] = {}
policy_definitions[policy_name][setting_name] = value
return policy_definitions