strcred.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # -*- test-case-name: twisted.cred.test.test_strcred -*-
  2. #
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. #
  6. """
  7. Support for resolving command-line strings that represent different
  8. checkers available to cred.
  9. Examples:
  10. - passwd:/etc/passwd
  11. - memory:admin:asdf:user:lkj
  12. - unix
  13. """
  14. from __future__ import absolute_import, division
  15. import sys
  16. from zope.interface import Interface, Attribute
  17. from twisted.plugin import getPlugins
  18. from twisted.python import usage
  19. class ICheckerFactory(Interface):
  20. """
  21. A factory for objects which provide
  22. L{twisted.cred.checkers.ICredentialsChecker}.
  23. It's implemented by twistd plugins creating checkers.
  24. """
  25. authType = Attribute(
  26. 'A tag that identifies the authentication method.')
  27. authHelp = Attribute(
  28. 'A detailed (potentially multi-line) description of precisely '
  29. 'what functionality this CheckerFactory provides.')
  30. argStringFormat = Attribute(
  31. 'A short (one-line) description of the argument string format.')
  32. credentialInterfaces = Attribute(
  33. 'A list of credentials interfaces that this factory will support.')
  34. def generateChecker(argstring):
  35. """
  36. Return an L{twisted.cred.checkers.ICredentialsChecker} provider using the supplied
  37. argument string.
  38. """
  39. class StrcredException(Exception):
  40. """
  41. Base exception class for strcred.
  42. """
  43. class InvalidAuthType(StrcredException):
  44. """
  45. Raised when a user provides an invalid identifier for the
  46. authentication plugin (known as the authType).
  47. """
  48. class InvalidAuthArgumentString(StrcredException):
  49. """
  50. Raised by an authentication plugin when the argument string
  51. provided is formatted incorrectly.
  52. """
  53. class UnsupportedInterfaces(StrcredException):
  54. """
  55. Raised when an application is given a checker to use that does not
  56. provide any of the application's supported credentials interfaces.
  57. """
  58. # This will be used to warn the users whenever they view help for an
  59. # authType that is not supported by the application.
  60. notSupportedWarning = ("WARNING: This authType is not supported by "
  61. "this application.")
  62. def findCheckerFactories():
  63. """
  64. Find all objects that implement L{ICheckerFactory}.
  65. """
  66. return getPlugins(ICheckerFactory)
  67. def findCheckerFactory(authType):
  68. """
  69. Find the first checker factory that supports the given authType.
  70. """
  71. for factory in findCheckerFactories():
  72. if factory.authType == authType:
  73. return factory
  74. raise InvalidAuthType(authType)
  75. def makeChecker(description):
  76. """
  77. Returns an L{twisted.cred.checkers.ICredentialsChecker} based on the
  78. contents of a descriptive string. Similar to
  79. L{twisted.application.strports}.
  80. """
  81. if ':' in description:
  82. authType, argstring = description.split(':', 1)
  83. else:
  84. authType = description
  85. argstring = ''
  86. return findCheckerFactory(authType).generateChecker(argstring)
  87. class AuthOptionMixin:
  88. """
  89. Defines helper methods that can be added on to any
  90. L{usage.Options} subclass that needs authentication.
  91. This mixin implements three new options methods:
  92. The opt_auth method (--auth) will write two new values to the
  93. 'self' dictionary: C{credInterfaces} (a dict of lists) and
  94. C{credCheckers} (a list).
  95. The opt_help_auth method (--help-auth) will search for all
  96. available checker plugins and list them for the user; it will exit
  97. when finished.
  98. The opt_help_auth_type method (--help-auth-type) will display
  99. detailed help for a particular checker plugin.
  100. @cvar supportedInterfaces: An iterable object that returns
  101. credential interfaces which this application is able to support.
  102. @cvar authOutput: A writeable object to which this options class
  103. will send all help-related output. Default: L{sys.stdout}
  104. """
  105. supportedInterfaces = None
  106. authOutput = sys.stdout
  107. def supportsInterface(self, interface):
  108. """
  109. Returns whether a particular credentials interface is supported.
  110. """
  111. return (self.supportedInterfaces is None
  112. or interface in self.supportedInterfaces)
  113. def supportsCheckerFactory(self, factory):
  114. """
  115. Returns whether a checker factory will provide at least one of
  116. the credentials interfaces that we care about.
  117. """
  118. for interface in factory.credentialInterfaces:
  119. if self.supportsInterface(interface):
  120. return True
  121. return False
  122. def addChecker(self, checker):
  123. """
  124. Supply a supplied credentials checker to the Options class.
  125. """
  126. # First figure out which interfaces we're willing to support.
  127. supported = []
  128. if self.supportedInterfaces is None:
  129. supported = checker.credentialInterfaces
  130. else:
  131. for interface in checker.credentialInterfaces:
  132. if self.supportsInterface(interface):
  133. supported.append(interface)
  134. if not supported:
  135. raise UnsupportedInterfaces(checker.credentialInterfaces)
  136. # If we get this far, then we know we can use this checker.
  137. if 'credInterfaces' not in self:
  138. self['credInterfaces'] = {}
  139. if 'credCheckers' not in self:
  140. self['credCheckers'] = []
  141. self['credCheckers'].append(checker)
  142. for interface in supported:
  143. self['credInterfaces'].setdefault(interface, []).append(checker)
  144. def opt_auth(self, description):
  145. """
  146. Specify an authentication method for the server.
  147. """
  148. try:
  149. self.addChecker(makeChecker(description))
  150. except UnsupportedInterfaces as e:
  151. raise usage.UsageError(
  152. 'Auth plugin not supported: %s' % e.args[0])
  153. except InvalidAuthType as e:
  154. raise usage.UsageError(
  155. 'Auth plugin not recognized: %s' % e.args[0])
  156. except Exception as e:
  157. raise usage.UsageError('Unexpected error: %s' % e)
  158. def _checkerFactoriesForOptHelpAuth(self):
  159. """
  160. Return a list of which authTypes will be displayed by --help-auth.
  161. This makes it a lot easier to test this module.
  162. """
  163. for factory in findCheckerFactories():
  164. for interface in factory.credentialInterfaces:
  165. if self.supportsInterface(interface):
  166. yield factory
  167. break
  168. def opt_help_auth(self):
  169. """
  170. Show all authentication methods available.
  171. """
  172. self.authOutput.write("Usage: --auth AuthType[:ArgString]\n")
  173. self.authOutput.write("For detailed help: --help-auth-type AuthType\n")
  174. self.authOutput.write('\n')
  175. # Figure out the right width for our columns
  176. firstLength = 0
  177. for factory in self._checkerFactoriesForOptHelpAuth():
  178. if len(factory.authType) > firstLength:
  179. firstLength = len(factory.authType)
  180. formatString = ' %%-%is\t%%s\n' % firstLength
  181. self.authOutput.write(formatString % ('AuthType', 'ArgString format'))
  182. self.authOutput.write(formatString % ('========', '================'))
  183. for factory in self._checkerFactoriesForOptHelpAuth():
  184. self.authOutput.write(
  185. formatString % (factory.authType, factory.argStringFormat))
  186. self.authOutput.write('\n')
  187. raise SystemExit(0)
  188. def opt_help_auth_type(self, authType):
  189. """
  190. Show help for a particular authentication type.
  191. """
  192. try:
  193. cf = findCheckerFactory(authType)
  194. except InvalidAuthType:
  195. raise usage.UsageError("Invalid auth type: %s" % authType)
  196. self.authOutput.write("Usage: --auth %s[:ArgString]\n" % authType)
  197. self.authOutput.write("ArgString format: %s\n" % cf.argStringFormat)
  198. self.authOutput.write('\n')
  199. for line in cf.authHelp.strip().splitlines():
  200. self.authOutput.write(' %s\n' % line.rstrip())
  201. self.authOutput.write('\n')
  202. if not self.supportsCheckerFactory(cf):
  203. self.authOutput.write(' %s\n' % notSupportedWarning)
  204. self.authOutput.write('\n')
  205. raise SystemExit(0)