getting file size for all dict files to be downloaded. coming to be 400mb or so.
[worddb.git] / libs / openid / association.py
blob5be534b9a815f5030bc0762f6920dce75b24b0ae
1 # -*- test-case-name: openid.test.test_association -*-
2 """
3 This module contains code for dealing with associations between
4 consumers and servers. Associations contain a shared secret that is
5 used to sign C{openid.mode=id_res} messages.
7 Users of the library should not usually need to interact directly with
8 associations. The L{store<openid.store>},
9 L{server<openid.server.server>} and
10 L{consumer<openid.consumer.consumer>} objects will create and manage
11 the associations. The consumer and server code will make use of a
12 C{L{SessionNegotiator}} when managing associations, which enables
13 users to express a preference for what kind of associations should be
14 allowed, and what kind of exchange should be done to establish the
15 association.
17 @var default_negotiator: A C{L{SessionNegotiator}} that allows all
18 association types that are specified by the OpenID
19 specification. It prefers to use HMAC-SHA1/DH-SHA1, if it's
20 available. If HMAC-SHA256 is not supported by your Python runtime,
21 HMAC-SHA256 and DH-SHA256 will not be available.
23 @var encrypted_negotiator: A C{L{SessionNegotiator}} that
24 does not support C{'no-encryption'} associations. It prefers
25 HMAC-SHA1/DH-SHA1 association types if available.
26 """
28 __all__ = [
29 'default_negotiator',
30 'encrypted_negotiator',
31 'SessionNegotiator',
32 'Association',
35 import time
37 from openid import cryptutil
38 from openid import kvform
39 from openid import oidutil
40 from openid.message import OPENID_NS
42 all_association_types = [
43 'HMAC-SHA1',
44 'HMAC-SHA256',
47 if hasattr(cryptutil, 'hmacSha256'):
48 supported_association_types = list(all_association_types)
50 default_association_order = [
51 ('HMAC-SHA1', 'DH-SHA1'),
52 ('HMAC-SHA1', 'no-encryption'),
53 ('HMAC-SHA256', 'DH-SHA256'),
54 ('HMAC-SHA256', 'no-encryption'),
57 only_encrypted_association_order = [
58 ('HMAC-SHA1', 'DH-SHA1'),
59 ('HMAC-SHA256', 'DH-SHA256'),
61 else:
62 supported_association_types = ['HMAC-SHA1']
64 default_association_order = [
65 ('HMAC-SHA1', 'DH-SHA1'),
66 ('HMAC-SHA1', 'no-encryption'),
69 only_encrypted_association_order = [
70 ('HMAC-SHA1', 'DH-SHA1'),
73 def getSessionTypes(assoc_type):
74 """Return the allowed session types for a given association type"""
75 assoc_to_session = {
76 'HMAC-SHA1': ['DH-SHA1', 'no-encryption'],
77 'HMAC-SHA256': ['DH-SHA256', 'no-encryption'],
79 return assoc_to_session.get(assoc_type, [])
81 def checkSessionType(assoc_type, session_type):
82 """Check to make sure that this pair of assoc type and session
83 type are allowed"""
84 if session_type not in getSessionTypes(assoc_type):
85 raise ValueError(
86 'Session type %r not valid for assocation type %r'
87 % (session_type, assoc_type))
89 class SessionNegotiator(object):
90 """A session negotiator controls the allowed and preferred
91 association types and association session types. Both the
92 C{L{Consumer<openid.consumer.consumer.Consumer>}} and
93 C{L{Server<openid.server.server.Server>}} use negotiators when
94 creating associations.
96 You can create and use negotiators if you:
98 - Do not want to do Diffie-Hellman key exchange because you use
99 transport-layer encryption (e.g. SSL)
101 - Want to use only SHA-256 associations
103 - Do not want to support plain-text associations over a non-secure
104 channel
106 It is up to you to set a policy for what kinds of associations to
107 accept. By default, the library will make any kind of association
108 that is allowed in the OpenID 2.0 specification.
110 Use of negotiators in the library
111 =================================
113 When a consumer makes an association request, it calls
114 C{L{getAllowedType}} to get the preferred association type and
115 association session type.
117 The server gets a request for a particular association/session
118 type and calls C{L{isAllowed}} to determine if it should
119 create an association. If it is supported, negotiation is
120 complete. If it is not, the server calls C{L{getAllowedType}} to
121 get an allowed association type to return to the consumer.
123 If the consumer gets an error response indicating that the
124 requested association/session type is not supported by the server
125 that contains an assocation/session type to try, it calls
126 C{L{isAllowed}} to determine if it should try again with the
127 given combination of association/session type.
129 @ivar allowed_types: A list of association/session types that are
130 allowed by the server. The order of the pairs in this list
131 determines preference. If an association/session type comes
132 earlier in the list, the library is more likely to use that
133 type.
134 @type allowed_types: [(str, str)]
137 def __init__(self, allowed_types):
138 self.setAllowedTypes(allowed_types)
140 def copy(self):
141 return self.__class__(list(self.allowed_types))
143 def setAllowedTypes(self, allowed_types):
144 """Set the allowed association types, checking to make sure
145 each combination is valid."""
146 for (assoc_type, session_type) in allowed_types:
147 checkSessionType(assoc_type, session_type)
149 self.allowed_types = allowed_types
151 def addAllowedType(self, assoc_type, session_type=None):
152 """Add an association type and session type to the allowed
153 types list. The assocation/session pairs are tried in the
154 order that they are added."""
155 if self.allowed_types is None:
156 self.allowed_types = []
158 if session_type is None:
159 available = getSessionTypes(assoc_type)
161 if not available:
162 raise ValueError('No session available for association type %r'
163 % (assoc_type,))
165 for session_type in getSessionTypes(assoc_type):
166 self.addAllowedType(assoc_type, session_type)
167 else:
168 checkSessionType(assoc_type, session_type)
169 self.allowed_types.append((assoc_type, session_type))
172 def isAllowed(self, assoc_type, session_type):
173 """Is this combination of association type and session type allowed?"""
174 assoc_good = (assoc_type, session_type) in self.allowed_types
175 matches = session_type in getSessionTypes(assoc_type)
176 return assoc_good and matches
178 def getAllowedType(self):
179 """Get a pair of assocation type and session type that are
180 supported"""
181 try:
182 return self.allowed_types[0]
183 except IndexError:
184 return (None, None)
186 default_negotiator = SessionNegotiator(default_association_order)
187 encrypted_negotiator = SessionNegotiator(only_encrypted_association_order)
189 def getSecretSize(assoc_type):
190 if assoc_type == 'HMAC-SHA1':
191 return 20
192 elif assoc_type == 'HMAC-SHA256':
193 return 32
194 else:
195 raise ValueError('Unsupported association type: %r' % (assoc_type,))
197 class Association(object):
199 This class represents an association between a server and a
200 consumer. In general, users of this library will never see
201 instances of this object. The only exception is if you implement
202 a custom C{L{OpenIDStore<openid.store.interface.OpenIDStore>}}.
204 If you do implement such a store, it will need to store the values
205 of the C{L{handle}}, C{L{secret}}, C{L{issued}}, C{L{lifetime}}, and
206 C{L{assoc_type}} instance variables.
208 @ivar handle: This is the handle the server gave this association.
210 @type handle: C{str}
213 @ivar secret: This is the shared secret the server generated for
214 this association.
216 @type secret: C{str}
219 @ivar issued: This is the time this association was issued, in
220 seconds since 00:00 GMT, January 1, 1970. (ie, a unix
221 timestamp)
223 @type issued: C{int}
226 @ivar lifetime: This is the amount of time this association is
227 good for, measured in seconds since the association was
228 issued.
230 @type lifetime: C{int}
233 @ivar assoc_type: This is the type of association this instance
234 represents. The only valid value of this field at this time
235 is C{'HMAC-SHA1'}, but new types may be defined in the future.
237 @type assoc_type: C{str}
240 @sort: __init__, fromExpiresIn, getExpiresIn, __eq__, __ne__,
241 handle, secret, issued, lifetime, assoc_type
244 # The ordering and name of keys as stored by serialize
245 assoc_keys = [
246 'version',
247 'handle',
248 'secret',
249 'issued',
250 'lifetime',
251 'assoc_type',
255 _macs = {
256 'HMAC-SHA1': cryptutil.hmacSha1,
257 'HMAC-SHA256': cryptutil.hmacSha256,
261 def fromExpiresIn(cls, expires_in, handle, secret, assoc_type):
263 This is an alternate constructor used by the OpenID consumer
264 library to create associations. C{L{OpenIDStore
265 <openid.store.interface.OpenIDStore>}} implementations
266 shouldn't use this constructor.
269 @param expires_in: This is the amount of time this association
270 is good for, measured in seconds since the association was
271 issued.
273 @type expires_in: C{int}
276 @param handle: This is the handle the server gave this
277 association.
279 @type handle: C{str}
282 @param secret: This is the shared secret the server generated
283 for this association.
285 @type secret: C{str}
288 @param assoc_type: This is the type of association this
289 instance represents. The only valid value of this field
290 at this time is C{'HMAC-SHA1'}, but new types may be
291 defined in the future.
293 @type assoc_type: C{str}
295 issued = int(time.time())
296 lifetime = expires_in
297 return cls(handle, secret, issued, lifetime, assoc_type)
299 fromExpiresIn = classmethod(fromExpiresIn)
301 def __init__(self, handle, secret, issued, lifetime, assoc_type):
303 This is the standard constructor for creating an association.
306 @param handle: This is the handle the server gave this
307 association.
309 @type handle: C{str}
312 @param secret: This is the shared secret the server generated
313 for this association.
315 @type secret: C{str}
318 @param issued: This is the time this association was issued,
319 in seconds since 00:00 GMT, January 1, 1970. (ie, a unix
320 timestamp)
322 @type issued: C{int}
325 @param lifetime: This is the amount of time this association
326 is good for, measured in seconds since the association was
327 issued.
329 @type lifetime: C{int}
332 @param assoc_type: This is the type of association this
333 instance represents. The only valid value of this field
334 at this time is C{'HMAC-SHA1'}, but new types may be
335 defined in the future.
337 @type assoc_type: C{str}
339 if assoc_type not in all_association_types:
340 fmt = '%r is not a supported association type'
341 raise ValueError(fmt % (assoc_type,))
343 # secret_size = getSecretSize(assoc_type)
344 # if len(secret) != secret_size:
345 # fmt = 'Wrong size secret (%s bytes) for association type %s'
346 # raise ValueError(fmt % (len(secret), assoc_type))
348 self.handle = handle
349 self.secret = secret
350 self.issued = issued
351 self.lifetime = lifetime
352 self.assoc_type = assoc_type
354 def getExpiresIn(self, now=None):
356 This returns the number of seconds this association is still
357 valid for, or C{0} if the association is no longer valid.
360 @return: The number of seconds this association is still valid
361 for, or C{0} if the association is no longer valid.
363 @rtype: C{int}
365 if now is None:
366 now = int(time.time())
368 return max(0, self.issued + self.lifetime - now)
370 expiresIn = property(getExpiresIn)
372 def __eq__(self, other):
374 This checks to see if two C{L{Association}} instances
375 represent the same association.
378 @return: C{True} if the two instances represent the same
379 association, C{False} otherwise.
381 @rtype: C{bool}
383 return type(self) is type(other) and self.__dict__ == other.__dict__
385 def __ne__(self, other):
387 This checks to see if two C{L{Association}} instances
388 represent different associations.
391 @return: C{True} if the two instances represent different
392 associations, C{False} otherwise.
394 @rtype: C{bool}
396 return not (self == other)
398 def serialize(self):
400 Convert an association to KV form.
402 @return: String in KV form suitable for deserialization by
403 deserialize.
405 @rtype: str
407 data = {
408 'version':'2',
409 'handle':self.handle,
410 'secret':oidutil.toBase64(self.secret),
411 'issued':str(int(self.issued)),
412 'lifetime':str(int(self.lifetime)),
413 'assoc_type':self.assoc_type
416 assert len(data) == len(self.assoc_keys)
417 pairs = []
418 for field_name in self.assoc_keys:
419 pairs.append((field_name, data[field_name]))
421 return kvform.seqToKV(pairs, strict=True)
423 def deserialize(cls, assoc_s):
425 Parse an association as stored by serialize().
427 inverse of serialize
430 @param assoc_s: Association as serialized by serialize()
432 @type assoc_s: str
435 @return: instance of this class
437 pairs = kvform.kvToSeq(assoc_s, strict=True)
438 keys = []
439 values = []
440 for k, v in pairs:
441 keys.append(k)
442 values.append(v)
444 if keys != cls.assoc_keys:
445 raise ValueError('Unexpected key values: %r', keys)
447 version, handle, secret, issued, lifetime, assoc_type = values
448 if version != '2':
449 raise ValueError('Unknown version: %r' % version)
450 issued = int(issued)
451 lifetime = int(lifetime)
452 secret = oidutil.fromBase64(secret)
453 return cls(handle, secret, issued, lifetime, assoc_type)
455 deserialize = classmethod(deserialize)
457 def sign(self, pairs):
459 Generate a signature for a sequence of (key, value) pairs
462 @param pairs: The pairs to sign, in order
464 @type pairs: sequence of (str, str)
467 @return: The binary signature of this sequence of pairs
469 @rtype: str
471 kv = kvform.seqToKV(pairs)
473 try:
474 mac = self._macs[self.assoc_type]
475 except KeyError:
476 raise ValueError(
477 'Unknown association type: %r' % (self.assoc_type,))
479 return mac(self.secret, kv)
482 def getMessageSignature(self, message):
483 """Return the signature of a message.
485 If I am not a sign-all association, the message must have a
486 signed list.
488 @return: the signature, base64 encoded
490 @rtype: str
492 @raises ValueError: If there is no signed list and I am not a sign-all
493 type of association.
495 pairs = self._makePairs(message)
496 return oidutil.toBase64(self.sign(pairs))
498 def signMessage(self, message):
499 """Add a signature (and a signed list) to a message.
501 @return: a new Message object with a signature
502 @rtype: L{openid.message.Message}
504 if (message.hasKey(OPENID_NS, 'sig') or
505 message.hasKey(OPENID_NS, 'signed')):
506 raise ValueError('Message already has signed list or signature')
508 extant_handle = message.getArg(OPENID_NS, 'assoc_handle')
509 if extant_handle and extant_handle != self.handle:
510 raise ValueError("Message has a different association handle")
512 signed_message = message.copy()
513 signed_message.setArg(OPENID_NS, 'assoc_handle', self.handle)
514 message_keys = signed_message.toPostArgs().keys()
515 signed_list = [k[7:] for k in message_keys
516 if k.startswith('openid.')]
517 signed_list.append('signed')
518 signed_list.sort()
519 signed_message.setArg(OPENID_NS, 'signed', ','.join(signed_list))
520 sig = self.getMessageSignature(signed_message)
521 signed_message.setArg(OPENID_NS, 'sig', sig)
522 return signed_message
524 def checkMessageSignature(self, message):
525 """Given a message with a signature, calculate a new signature
526 and return whether it matches the signature in the message.
528 @raises ValueError: if the message has no signature or no signature
529 can be calculated for it.
530 """
531 message_sig = message.getArg(OPENID_NS, 'sig')
532 if not message_sig:
533 raise ValueError("%s has no sig." % (message,))
534 calculated_sig = self.getMessageSignature(message)
535 return calculated_sig == message_sig
538 def _makePairs(self, message):
539 signed = message.getArg(OPENID_NS, 'signed')
540 if not signed:
541 raise ValueError('Message has no signed list: %s' % (message,))
543 signed_list = signed.split(',')
544 pairs = []
545 data = message.toPostArgs()
546 for field in signed_list:
547 pairs.append((field, data.get('openid.' + field, '')))
548 return pairs
550 def __repr__(self):
551 return "<%s.%s %s %s>" % (
552 self.__class__.__module__,
553 self.__class__.__name__,
554 self.assoc_type,
555 self.handle)