manager.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. class YadisServiceManager(object):
  2. """Holds the state of a list of selected Yadis services, managing
  3. storing it in a session and iterating over the services in order."""
  4. def __init__(self, starting_url, yadis_url, services, session_key):
  5. # The URL that was used to initiate the Yadis protocol
  6. self.starting_url = starting_url
  7. # The URL after following redirects (the identifier)
  8. self.yadis_url = yadis_url
  9. # List of service elements
  10. self.services = list(services)
  11. self.session_key = session_key
  12. # Reference to the current service object
  13. self._current = None
  14. def __len__(self):
  15. """How many untried services remain?"""
  16. return len(self.services)
  17. def __iter__(self):
  18. return self
  19. def next(self):
  20. """Return the next service
  21. self.current() will continue to return that service until the
  22. next call to this method."""
  23. try:
  24. self._current = self.services.pop(0)
  25. except IndexError:
  26. raise StopIteration
  27. else:
  28. return self._current
  29. def current(self):
  30. """Return the current service.
  31. Returns None if there are no services left.
  32. """
  33. return self._current
  34. def forURL(self, url):
  35. return url in [self.starting_url, self.yadis_url]
  36. def started(self):
  37. """Has the first service been returned?"""
  38. return self._current is not None
  39. def store(self, session):
  40. """Store this object in the session, by its session key."""
  41. session[self.session_key] = self
  42. class Discovery(object):
  43. """State management for discovery.
  44. High-level usage pattern is to call .getNextService(discover) in
  45. order to find the next available service for this user for this
  46. session. Once a request completes, call .finish() to clean up the
  47. session state.
  48. @ivar session: a dict-like object that stores state unique to the
  49. requesting user-agent. This object must be able to store
  50. serializable objects.
  51. @ivar url: the URL that is used to make the discovery request
  52. @ivar session_key_suffix: The suffix that will be used to identify
  53. this object in the session object.
  54. """
  55. DEFAULT_SUFFIX = 'auth'
  56. PREFIX = '_yadis_services_'
  57. def __init__(self, session, url, session_key_suffix=None):
  58. """Initialize a discovery object"""
  59. self.session = session
  60. self.url = url
  61. if session_key_suffix is None:
  62. session_key_suffix = self.DEFAULT_SUFFIX
  63. self.session_key_suffix = session_key_suffix
  64. def getNextService(self, discover):
  65. """Return the next authentication service for the pair of
  66. user_input and session. This function handles fallback.
  67. @param discover: a callable that takes a URL and returns a
  68. list of services
  69. @type discover: str -> [service]
  70. @return: the next available service
  71. """
  72. manager = self.getManager()
  73. if manager is not None and not manager:
  74. self.destroyManager()
  75. if not manager:
  76. yadis_url, services = discover(self.url)
  77. manager = self.createManager(services, yadis_url)
  78. if manager:
  79. service = manager.next()
  80. manager.store(self.session)
  81. else:
  82. service = None
  83. return service
  84. def cleanup(self, force=False):
  85. """Clean up Yadis-related services in the session and return
  86. the most-recently-attempted service from the manager, if one
  87. exists.
  88. @param force: True if the manager should be deleted regardless
  89. of whether it's a manager for self.url.
  90. @return: current service endpoint object or None if there is
  91. no current service
  92. """
  93. manager = self.getManager(force=force)
  94. if manager is not None:
  95. service = manager.current()
  96. self.destroyManager(force=force)
  97. else:
  98. service = None
  99. return service
  100. ### Lower-level methods
  101. def getSessionKey(self):
  102. """Get the session key for this starting URL and suffix
  103. @return: The session key
  104. @rtype: str
  105. """
  106. return self.PREFIX + self.session_key_suffix
  107. def getManager(self, force=False):
  108. """Extract the YadisServiceManager for this object's URL and
  109. suffix from the session.
  110. @param force: True if the manager should be returned
  111. regardless of whether it's a manager for self.url.
  112. @return: The current YadisServiceManager, if it's for this
  113. URL, or else None
  114. """
  115. manager = self.session.get(self.getSessionKey())
  116. if (manager is not None and (manager.forURL(self.url) or force)):
  117. return manager
  118. else:
  119. return None
  120. def createManager(self, services, yadis_url=None):
  121. """Create a new YadisService Manager for this starting URL and
  122. suffix, and store it in the session.
  123. @raises KeyError: When I already have a manager.
  124. @return: A new YadisServiceManager or None
  125. """
  126. key = self.getSessionKey()
  127. if self.getManager():
  128. raise KeyError('There is already a %r manager for %r' %
  129. (key, self.url))
  130. if not services:
  131. return None
  132. manager = YadisServiceManager(self.url, yadis_url, services, key)
  133. manager.store(self.session)
  134. return manager
  135. def destroyManager(self, force=False):
  136. """Delete any YadisServiceManager with this starting URL and
  137. suffix from the session.
  138. If there is no service manager or the service manager is for a
  139. different URL, it silently does nothing.
  140. @param force: True if the manager should be deleted regardless
  141. of whether it's a manager for self.url.
  142. """
  143. if self.getManager(force=force) is not None:
  144. key = self.getSessionKey()
  145. del self.session[key]