1 """An implementation of the OpenID Provider Authentication Policy
2 Extension 1.0
3
4 @see: http://openid.net/developers/specs/
5
6 @since: 2.1.0
7 """
8
9 __all__ = [
10 'Request',
11 'Response',
12 'ns_uri',
13 'AUTH_PHISHING_RESISTANT',
14 'AUTH_MULTI_FACTOR',
15 'AUTH_MULTI_FACTOR_PHYSICAL',
16 ]
17
18 from openid.extension import Extension
19 import re
20
21 ns_uri = "http://specs.openid.net/extensions/pape/1.0"
22
23 AUTH_MULTI_FACTOR_PHYSICAL = \
24 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical'
25 AUTH_MULTI_FACTOR = \
26 'http://schemas.openid.net/pape/policies/2007/06/multi-factor'
27 AUTH_PHISHING_RESISTANT = \
28 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant'
29
30 TIME_VALIDATOR = re.compile('^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$')
31
33 """A Provider Authentication Policy request, sent from a relying
34 party to a provider
35
36 @ivar preferred_auth_policies: The authentication policies that
37 the relying party prefers
38 @type preferred_auth_policies: [str]
39
40 @ivar max_auth_age: The maximum time, in seconds, that the relying
41 party wants to allow to have elapsed before the user must
42 re-authenticate
43 @type max_auth_age: int or NoneType
44 """
45
46 ns_alias = 'pape'
47
48 - def __init__(self, preferred_auth_policies=None, max_auth_age=None):
49 super(Request, self).__init__()
50 if not preferred_auth_policies:
51 preferred_auth_policies = []
52
53 self.preferred_auth_policies = preferred_auth_policies
54 self.max_auth_age = max_auth_age
55
57 return bool(self.preferred_auth_policies or
58 self.max_auth_age is not None)
59
61 """Add an acceptable authentication policy URI to this request
62
63 This method is intended to be used by the relying party to add
64 acceptable authentication types to the request.
65
66 @param policy_uri: The identifier for the preferred type of
67 authentication.
68 @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
69 """
70 if policy_uri not in self.preferred_auth_policies:
71 self.preferred_auth_policies.append(policy_uri)
72
74 """@see: C{L{Extension.getExtensionArgs}}
75 """
76 ns_args = {
77 'preferred_auth_policies':' '.join(self.preferred_auth_policies)
78 }
79
80 if self.max_auth_age is not None:
81 ns_args['max_auth_age'] = str(self.max_auth_age)
82
83 return ns_args
84
86 """Instantiate a Request object from the arguments in a
87 C{checkid_*} OpenID message
88 """
89 self = cls()
90 args = request.message.getArgs(self.ns_uri)
91
92 if args == {}:
93 return None
94
95 self.parseExtensionArgs(args)
96 return self
97
98 fromOpenIDRequest = classmethod(fromOpenIDRequest)
99
101 """Set the state of this request to be that expressed in these
102 PAPE arguments
103
104 @param args: The PAPE arguments without a namespace
105
106 @rtype: None
107
108 @raises ValueError: When the max_auth_age is not parseable as
109 an integer
110 """
111
112
113 self.preferred_auth_policies = []
114
115 policies_str = args.get('preferred_auth_policies')
116 if policies_str:
117 for uri in policies_str.split(' '):
118 if uri not in self.preferred_auth_policies:
119 self.preferred_auth_policies.append(uri)
120
121
122 max_auth_age_str = args.get('max_auth_age')
123 self.max_auth_age = None
124
125 if max_auth_age_str:
126 try:
127 self.max_auth_age = int(max_auth_age_str)
128 except ValueError:
129 pass
130
132 """Given a list of authentication policy URIs that a provider
133 supports, this method returns the subsequence of those types
134 that are preferred by the relying party.
135
136 @param supported_types: A sequence of authentication policy
137 type URIs that are supported by a provider
138
139 @returns: The sub-sequence of the supported types that are
140 preferred by the relying party. This list will be ordered
141 in the order that the types appear in the supported_types
142 sequence, and may be empty if the provider does not prefer
143 any of the supported authentication types.
144
145 @returntype: [str]
146 """
147 return filter(self.preferred_auth_policies.__contains__,
148 supported_types)
149
150 Request.ns_uri = ns_uri
151
152
154 """A Provider Authentication Policy response, sent from a provider
155 to a relying party
156 """
157
158 ns_alias = 'pape'
159
160 - def __init__(self, auth_policies=None, auth_time=None,
161 nist_auth_level=None):
170
172 """Add a authentication policy to this response
173
174 This method is intended to be used by the provider to add a
175 policy that the provider conformed to when authenticating the user.
176
177 @param policy_uri: The identifier for the preferred type of
178 authentication.
179 @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
180 """
181 if policy_uri not in self.auth_policies:
182 self.auth_policies.append(policy_uri)
183
185 """Create a C{L{Response}} object from a successful OpenID
186 library response
187 (C{L{openid.consumer.consumer.SuccessResponse}}) response
188 message
189
190 @param success_response: A SuccessResponse from consumer.complete()
191 @type success_response: C{L{openid.consumer.consumer.SuccessResponse}}
192
193 @rtype: Response or None
194 @returns: A provider authentication policy response from the
195 data that was supplied with the C{id_res} response or None
196 if the provider sent no signed PAPE response arguments.
197 """
198 self = cls()
199
200
201 args = success_response.getSignedNS(self.ns_uri)
202
203
204
205 if args is not None:
206 self.parseExtensionArgs(args)
207 return self
208 else:
209 return None
210
212 """Parse the provider authentication policy arguments into the
213 internal state of this object
214
215 @param args: unqualified provider authentication policy
216 arguments
217
218 @param strict: Whether to raise an exception when bad data is
219 encountered
220
221 @returns: None. The data is parsed into the internal fields of
222 this object.
223 """
224 policies_str = args.get('auth_policies')
225 if policies_str and policies_str != 'none':
226 self.auth_policies = policies_str.split(' ')
227
228 nist_level_str = args.get('nist_auth_level')
229 if nist_level_str:
230 try:
231 nist_level = int(nist_level_str)
232 except ValueError:
233 if strict:
234 raise ValueError('nist_auth_level must be an integer between '
235 'zero and four, inclusive')
236 else:
237 self.nist_auth_level = None
238 else:
239 if 0 <= nist_level < 5:
240 self.nist_auth_level = nist_level
241
242 auth_time = args.get('auth_time')
243 if auth_time:
244 if TIME_VALIDATOR.match(auth_time):
245 self.auth_time = auth_time
246 elif strict:
247 raise ValueError("auth_time must be in RFC3339 format")
248
249 fromSuccessResponse = classmethod(fromSuccessResponse)
250
252 """@see: C{L{Extension.getExtensionArgs}}
253 """
254 if len(self.auth_policies) == 0:
255 ns_args = {
256 'auth_policies':'none',
257 }
258 else:
259 ns_args = {
260 'auth_policies':' '.join(self.auth_policies),
261 }
262
263 if self.nist_auth_level is not None:
264 if self.nist_auth_level not in range(0, 5):
265 raise ValueError('nist_auth_level must be an integer between '
266 'zero and four, inclusive')
267 ns_args['nist_auth_level'] = str(self.nist_auth_level)
268
269 if self.auth_time is not None:
270 if not TIME_VALIDATOR.match(self.auth_time):
271 raise ValueError('auth_time must be in RFC3339 format')
272
273 ns_args['auth_time'] = self.auth_time
274
275 return ns_args
276
277 Response.ns_uri = ns_uri
278