plugins.rst 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. .. _about_plugins:
  2. About :mod:`repoze.who` Plugins
  3. ===============================
  4. Plugin Types
  5. ------------
  6. Identifier Plugins
  7. ++++++++++++++++++
  8. You can register a plugin as willing to act as an "identifier". An
  9. identifier examines the WSGI environment and attempts to extract
  10. credentials from the environment. These credentials are used by
  11. authenticator plugins to perform authentication.
  12. Authenticator Plugins
  13. +++++++++++++++++++++
  14. You may register a plugin as willing to act as an "authenticator".
  15. Authenticator plugins are responsible for resolving a set of
  16. credentials provided by an identifier plugin into a user id.
  17. Typically, authenticator plugins will perform a lookup into a database
  18. or some other persistent store, check the provided credentials against
  19. the stored data, and return a user id if the credentials can be
  20. validated.
  21. The user id provided by an authenticator is eventually passed to
  22. downstream WSGI applications in the "REMOTE_USER' environment
  23. variable. Additionally, the "identity" of the user (as provided by
  24. the identifier from whence the identity came) is passed along to
  25. downstream application in the ``repoze.who.identity`` environment
  26. variable.
  27. Metadata Provider Plugins
  28. +++++++++++++++++++++++++
  29. You may register a plugin as willing to act as a "metadata provider"
  30. (aka mdprovider). Metadata provider plugins are responsible for
  31. adding arbitrary information to the identity dictionary for
  32. consumption by downstream applications. For instance, a metadata
  33. provider plugin may add "group" information to the the identity.
  34. Challenger Plugins
  35. ++++++++++++++++++
  36. You may register a plugin as willing to act as a "challenger".
  37. Challenger plugins are responsible for initiating a challenge to the
  38. requesting user. Challenger plugins are invoked by :mod:`repoze.who` when it
  39. decides a challenge is necessary. A challenge might consist of
  40. displaying a form or presenting the user with a basic or digest
  41. authentication dialog.
  42. .. _default_plugins:
  43. Default Plugin Implementations
  44. ------------------------------
  45. :mod:`repoze.who` ships with a variety of default plugins that do
  46. authentication, identification, challenge and metadata provision.
  47. .. module:: repoze.who.plugins.auth_tkt
  48. .. class:: AuthTktCookiePlugin(secret [, cookie_name='auth_tkt' [, secure=False [, include_ip=False]]])
  49. An :class:`AuthTktCookiePlugin` is an ``IIdentifier`` and ``IAuthenticator``
  50. plugin which remembers its identity state in a client-side cookie.
  51. This plugin uses the ``paste.auth.auth_tkt``"auth ticket" protocol and
  52. is compatible with Apache's mod_auth_tkt.
  53. It should be instantiated passing a *secret*, which is used to encrypt the
  54. cookie on the client side and decrypt the cookie on the server side.
  55. The cookie name used to store the cookie value can be specified
  56. using the *cookie_name* parameter. If *secure* is False, the cookie
  57. will be sent across any HTTP or HTTPS connection; if it is True, the
  58. cookie will be sent only across an HTTPS connection. If
  59. *include_ip* is True, the ``REMOTE_ADDR`` of the WSGI environment
  60. will be placed in the cookie.
  61. Normally, using the plugin as an identifier requires also using it as
  62. an authenticator.
  63. .. note::
  64. Using the *include_ip* setting for public-facing applications may
  65. cause problems for some users. `One study
  66. <http://westpoint.ltd.uk/advisories/Paul_Johnston_GSEC.pdf>`_ reports
  67. that as many as 3% of users change their IP addresses legitimately
  68. during a session.
  69. .. note::
  70. Plugin supports remembering user data in the cookie by saving user dict into ``identity['userdata']``
  71. parameter of ``remember`` method. They are sent unencrypted and protected by checksum.
  72. Data will then be returned every time by ``identify``. This dict must be compatible with
  73. ``urllib.urlencode`` function (``urllib.urlparse.urlencode`` in python 3).
  74. Saving keys/values with unicode characters is supported only under python 3.
  75. .. note::
  76. Plugin supports multiple digest algorithms. It defaults to md5 to match
  77. the default for mod_auth_tkt and paste.auth.auth_tkt. However md5 is not
  78. recommended as there are viable attacks against the hash. Any algorithm
  79. from the hashlib library can be specified, currently only sha256 and sha512
  80. are supported by mod_auth_tkt.
  81. .. module:: repoze.who.plugins.basicauth
  82. .. class:: BasicAuthPlugin(realm)
  83. A :class:`BasicAuthPlugin` plugin is both an ``IIdentifier`` and
  84. ``IChallenger`` plugin that implements the Basic Access
  85. Authentication scheme described in :rfc:`2617`. It looks for
  86. credentials within the ``HTTP-Authorization`` header sent by
  87. browsers. It challenges by sending an ``WWW-Authenticate`` header
  88. to the browser. The single argument *realm* indicates the basic
  89. auth realm that should be sent in the ``WWW-Authenticate`` header.
  90. .. module:: repoze.who.plugins.htpasswd
  91. .. class:: HTPasswdPlugin(filename, check)
  92. A :class:`HTPasswdPlugin` is an ``IAuthenticator`` implementation
  93. which compares identity information against an Apache-style htpasswd
  94. file. The *filename* argument should be an absolute path to the
  95. htpasswd file' the *check* argument is a callable which takes two
  96. arguments: "password" and "hashed", where the "password" argument is
  97. the unencrypted password provided by the identifier plugin, and the
  98. hashed value is the value stored in the htpasswd file. If the
  99. hashed value of the password matches the hash, this callable should
  100. return True. A default implementation named ``crypt_check`` is
  101. available for use as a check function (on UNIX) as
  102. ``repoze.who.plugins.htpasswd:crypt_check``; it assumes the values
  103. in the htpasswd file are encrypted with the UNIX ``crypt`` function.
  104. .. module:: repoze.who.plugins.redirector
  105. .. class:: RedirectorPlugin(login_url, came_from_param, reason_param, reason_header)
  106. A :class:`RedirectorPlugin` is an ``IChallenger`` plugin.
  107. It redirects to a configured login URL at egress if a challenge is
  108. required .
  109. *login_url* is the URL that should be redirected to when a
  110. challenge is required. *came_from_param* is the name of an optional
  111. query string parameter: if configured, the plugin provides the current
  112. request URL in the redirected URL's query string, using the supplied
  113. parameter name. *reason_param* is the name of an optional
  114. query string parameter: if configured, and the application supplies
  115. a header matching *reason_header* (defaulting to
  116. ``X-Authorization-Failure-Reason``), the plugin includes that reason in
  117. the query string of the redirected URL, using the supplied parameter name.
  118. *reason_header* is an optional parameter overriding the default response
  119. header name (``X-Authorization-Failure-Reason``) which
  120. the plugin checks to find the application-supplied reason for the challenge.
  121. *reason_header* cannot be set unless *reason_param* is also set.
  122. .. module:: repoze.who.plugins.sql
  123. .. class:: SQLAuthenticatorPlugin(query, conn_factory, compare_fn)
  124. A :class:`SQLAuthenticatorPlugin` is an ``IAuthenticator``
  125. implementation which compares login-password identity information
  126. against data in an arbitrary SQL database. The *query* argument
  127. should be a SQL query that returns two columns in a single row
  128. considered to be the user id and the password respectively. The SQL
  129. query should contain Python-DBAPI style substitution values for
  130. ``%(login)``, e.g. ``SELECT user_id, password FROM users WHERE login
  131. = %(login)``. The *conn_factory* argument should be a callable that
  132. returns a DBAPI database connection. The *compare_fn* argument
  133. should be a callable that accepts two arguments: ``cleartext`` and
  134. ``stored_password_hash``. It should compare the hashed version of
  135. cleartext and return True if it matches the stored password hash,
  136. otherwise it should return False. A comparison function named
  137. ``default_password_compare`` exists in the
  138. ``repoze.who.plugins.sql`` module demonstrating this. The
  139. :class:`SQLAuthenticatorPlugin`\'s ``authenticate`` method will
  140. return the user id of the user unchanged to :mod:`repoze.who`.
  141. .. class:: SQLMetadataProviderPlugin(name, query, conn_factory, filter)
  142. A :class:`SQLMetatadaProviderPlugin` is an ``IMetadataProvider``
  143. implementation which adds arbitrary metadata to the identity on
  144. ingress using data from an arbitrary SQL database. The *name*
  145. argument should be a string. It will be used as a key in the
  146. identity dictionary. The *query* argument should be a SQL query
  147. that returns arbitrary data from the database in a form that accepts
  148. Python-binding style DBAPI arguments. It should expect that a
  149. ``__userid`` value will exist in the dictionary that is bound. The
  150. SQL query should contain Python-DBAPI style substitution values for
  151. (at least) ``%(__userid)``, e.g. ``SELECT group FROM groups WHERE
  152. user_id = %(__userid)``. The *conn_factory* argument should be a
  153. callable that returns a DBAPI database connection. The *filter*
  154. argument should be a callable that accepts the result of the DBAPI
  155. ``fetchall`` based on the SQL query. It should massage the data
  156. into something that will be set in the environment under the *name*
  157. key.
  158. Writing :mod:`repoze.who` Plugins
  159. ---------------------------------
  160. :mod:`repoze.who` can be extended arbitrarily through the creation of
  161. plugins. Plugins are of one of four types: identifier plugins,
  162. authenticator plugins, metadata provider plugins, and challenge
  163. plugins.
  164. Writing An Identifier Plugin
  165. ++++++++++++++++++++++++++++
  166. An identifier plugin (aka an ``IIdentifier`` plugin) must do three
  167. things: extract credentials from the request and turn them into an
  168. "identity", "remember" credentials, and "forget" credentials.
  169. Here's a simple cookie identification plugin that does these three
  170. things ::
  171. class InsecureCookiePlugin(object):
  172. def __init__(self, cookie_name):
  173. self.cookie_name = cookie_name
  174. def identify(self, environ):
  175. from paste.request import get_cookies
  176. cookies = get_cookies(environ)
  177. cookie = cookies.get(self.cookie_name)
  178. if cookie is None:
  179. return None
  180. import binascii
  181. try:
  182. auth = cookie.value.decode('base64')
  183. except binascii.Error: # can't decode
  184. return None
  185. try:
  186. login, password = auth.split(':', 1)
  187. return {'login':login, 'password':password}
  188. except ValueError: # not enough values to unpack
  189. return None
  190. def remember(self, environ, identity):
  191. cookie_value = '%(login)s:%(password)s' % identity
  192. cookie_value = cookie_value.encode('base64').rstrip()
  193. from paste.request import get_cookies
  194. cookies = get_cookies(environ)
  195. existing = cookies.get(self.cookie_name)
  196. value = getattr(existing, 'value', None)
  197. if value != cookie_value:
  198. # return a Set-Cookie header
  199. set_cookie = '%s=%s; Path=/;' % (self.cookie_name, cookie_value)
  200. return [('Set-Cookie', set_cookie)]
  201. def forget(self, environ, identity):
  202. # return a expires Set-Cookie header
  203. expired = ('%s=""; Path=/; Expires=Sun, 10-May-1971 11:59:00 GMT' %
  204. self.cookie_name)
  205. return [('Set-Cookie', expired)]
  206. def __repr__(self):
  207. return '<%s %s>' % (self.__class__.__name__, id(self))
  208. .identify
  209. ~~~~~~~~~
  210. The ``identify`` method of our InsecureCookiePlugin accepts a single
  211. argument "environ". This will be the WSGI environment dictionary.
  212. Our plugin attempts to grub through the cookies sent by the client,
  213. trying to find one that matches our cookie name. If it finds one that
  214. matches, it attempts to decode it and turn it into a login and a
  215. password, which it returns as values in a dictionary. This dictionary
  216. is thereafter known as an "identity". If it finds no credentials in
  217. cookies, it returns None (which is not considered an identity).
  218. More generally, the ``identify`` method of an ``IIdentifier`` plugin
  219. is called once on WSGI request "ingress", and it is expected to grub
  220. arbitrarily through the WSGI environment looking for credential
  221. information. In our above plugin, the credential information is
  222. expected to be in a cookie but credential information could be in a
  223. cookie, a form field, basic/digest auth information, a header, a WSGI
  224. environment variable set by some upstream middleware or whatever else
  225. someone might use to stash authentication information. If the plugin
  226. finds credentials in the request, it's expected to return an
  227. "identity": this must be a dictionary. The dictionary is not required
  228. to have any particular keys or value composition, although it's wise
  229. if the identification plugin looks for both a login name and a
  230. password information to return at least {'login':login_name,
  231. 'password':password}, as some authenticator plugins may depend on
  232. presence of the names "login" and "password" (e.g. the htpasswd and
  233. sql ``IAuthenticator`` plugins). If an ``IIdentifier`` plugin finds
  234. no credentials, it is expected to return None.
  235. .remember
  236. ~~~~~~~~~
  237. If we've passed a REMOTE_USER to the WSGI application during ingress
  238. (as a result of providing an identity that could be authenticated),
  239. and the downstream application doesn't kick back with an unauthorized
  240. response, on egress we want the requesting client to "remember" the
  241. identity we provided if there's some way to do that and if he hasn't
  242. already, in order to ensure he will pass it back to us on subsequent
  243. requests without requiring another login. The remember method of an
  244. ``IIdentifier`` plugin is called for each non-unauthenticated
  245. response. It is the responsibility of the ``IIdentifier`` plugin to
  246. conditionally return HTTP headers that will cause the client to
  247. remember the credentials implied by "identity".
  248. Our InsecureCookiePlugin implements the "remember" method by returning
  249. headers which set a cookie if and only if one is not already set with
  250. the same name and value in the WSGI environment. These headers will
  251. be tacked on to the response headers provided by the downstream
  252. application during the response.
  253. When you write a remember method, most of the work involved is
  254. determining *whether or not* you need to return headers. It's typical
  255. to see remember methods that compute an "old state" and a "new state"
  256. and compare the two against each other in order to determine if
  257. headers need to be returned. In our example InsecureCookiePlugin, the
  258. "old state" is ``cookie_value`` and the "new state" is ``value``.
  259. .forget
  260. ~~~~~~~
  261. Eventually the WSGI application we're serving will issue a "401
  262. Unauthorized" or another status signifying that the request could not
  263. be authorized. :mod:`repoze.who` intercepts this status and calls
  264. ``IIdentifier`` plugins asking them to "forget" the credentials
  265. implied by the identity. It is the "forget" method's job at this
  266. point to return HTTP headers that will effectively clear any
  267. credentials on the requesting client implied by the "identity"
  268. argument.
  269. Our InsecureCookiePlugin implements the "forget" method by returning
  270. a header which resets the cookie that was set earlier by the remember
  271. method to one that expires in the past (on my birthday, in fact).
  272. This header will be tacked onto the response headers provided by the
  273. downstream application.
  274. Writing an Authenticator Plugin
  275. +++++++++++++++++++++++++++++++
  276. An authenticator plugin (aka an ``IAuthenticator`` plugin) must do
  277. only one thing (on "ingress"): accept an identity and check if the
  278. identity is "good". If the identity is good, it should return a "user
  279. id". This user id may or may not be the same as the "login" provided
  280. by the user. An ``IAuthenticator`` plugin will be called for each
  281. identity found during the identification phase (there may be multiple
  282. identities for a single request, as there may be multiple
  283. ``IIdentifier`` plugins active at any given time), so it may be called
  284. multiple times in the same request.
  285. Here's a simple authenticator plugin that attempts to match an
  286. identity against ones defined in an "htpasswd" file that does just
  287. that::
  288. class SimpleHTPasswdPlugin(object):
  289. def __init__(self, filename):
  290. self.filename = filename
  291. # IAuthenticatorPlugin
  292. def authenticate(self, environ, identity):
  293. try:
  294. login = identity['login']
  295. password = identity['password']
  296. except KeyError:
  297. return None
  298. f = open(self.filename, 'r')
  299. for line in f:
  300. try:
  301. username, hashed = line.rstrip().split(':', 1)
  302. except ValueError:
  303. continue
  304. if username == login:
  305. if crypt_check(password, hashed):
  306. return username
  307. return None
  308. def crypt_check(password, hashed):
  309. from crypt import crypt
  310. salt = hashed[:2]
  311. return hashed == crypt(password, salt)
  312. An ``IAuthenticator`` plugin implements one "interface" method:
  313. "authentictate". The formal specification for the arguments and
  314. return values expected from these methods are available in the
  315. ``interfaces.py`` file in :mod:`repoze.who` as the ``IAuthenticator``
  316. interface, but let's examine this method here less formally.
  317. .authenticate
  318. ~~~~~~~~~~~~~
  319. The ``authenticate`` method accepts two arguments: the WSGI
  320. environment and an identity. Our SimpleHTPasswdPlugin
  321. ``authenticate`` implementation grabs the login and password out of
  322. the identity and attempts to find the login in the htpasswd file. If
  323. it finds it, it compares the crypted version of the password provided
  324. by the user to the crypted version stored in the htpasswd file, and
  325. finally, if they match, it returns the login. If they do not match,
  326. it returns None.
  327. .. note::
  328. Our plugin's ``authenticate`` method does not assume that the keys
  329. ``login`` or ``password`` exist in the identity; although it
  330. requires them to do "real work" it returns None if they are not
  331. present instead of raising an exception. This is required by the
  332. ``IAuthenticator`` interface specification.
  333. Writing a Challenger Plugin
  334. +++++++++++++++++++++++++++
  335. A challenger plugin (aka an ``IChallenger`` plugin) must do only one
  336. thing on "egress": return a WSGI application which performs a
  337. "challenge". A WSGI application is a callable that accepts an
  338. "environ" and a "start_response" as its parameters; see "PEP 333" for
  339. further definition of what a WSGI application is. A challenge asks
  340. the user for credentials.
  341. Here's an example of a simple challenger plugin::
  342. from paste.httpheaders import WWW_AUTHENTICATE
  343. from paste.httpexceptions import HTTPUnauthorized
  344. class BasicAuthChallengerPlugin(object):
  345. def __init__(self, realm):
  346. self.realm = realm
  347. # IChallenger
  348. def challenge(self, environ, status, app_headers, forget_headers):
  349. head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
  350. if head[0] not in forget_headers:
  351. head = head + forget_headers
  352. return HTTPUnauthorized(headers=head)
  353. Note that the plugin implements a single "interface" method:
  354. "challenge". The formal specification for the arguments and return
  355. values expected from this method is available in the "interfaces.py"
  356. file in :mod:`repoze.who` as the ``IChallenger`` interface. This method
  357. is called when :mod:`repoze.who` determines that the application has
  358. returned an "unauthorized" response (e.g. a 401). Only one challenger
  359. will be consulted during "egress" as necessary (the first one to
  360. return a non-None response).
  361. .challenge
  362. ~~~~~~~~~~
  363. The challenge method takes environ (the WSGI environment), 'status'
  364. (the status as set by the downstream application), the "app_headers"
  365. (headers returned by the application), and the "forget_headers"
  366. (headers returned by all participating ``IIdentifier`` plugins whom
  367. were asked to "forget" this user).
  368. Our BasicAuthChallengerPlugin takes advantage of the fact that the
  369. HTTPUnauthorized exception imported from paste.httpexceptions can be
  370. used as a WSGI application. It first makes sure that we don't repeat
  371. headers if an identification plugin has already set a
  372. "WWW-Authenticate" header like ours, then it returns an instance of
  373. HTTPUnauthorized, passing in merged headers. This will cause a basic
  374. authentication dialog to be presented to the user.
  375. Writing a Metadata Provider Plugin
  376. ++++++++++++++++++++++++++++++++++
  377. A metadata provider plugin (aka an ``IMetadataProvider`` plugin) must
  378. do only one thing (on "ingress"): "scribble" on the identity
  379. dictionary provided to it when it is called. An ``IMetadataProvider``
  380. plugin will be called with the final "best" identity found during the
  381. authentication phase, or not at all if no "best" identity could be
  382. authenticated. Thus, each ``IMetadataProvider`` plugin will be called
  383. exactly zero or one times during a request.
  384. Here's a simple metadata provider plugin that provides "property"
  385. information from a dictionary::
  386. _DATA = {
  387. 'chris': {'first_name':'Chris', 'last_name':'McDonough'} ,
  388. 'whit': {'first_name':'Whit', 'last_name':'Morriss'}
  389. }
  390. class SimpleMetadataProvider(object):
  391. def add_metadata(self, environ, identity):
  392. userid = identity.get('repoze.who.userid')
  393. info = _DATA.get(userid)
  394. if info is not None:
  395. identity.update(info)
  396. .add_metadata
  397. ~~~~~~~~~~~~~
  398. Arbitrarily add information to the identity dict based in other data
  399. in the environment or identity. Our plugin adds ``first_name`` and
  400. ``last_name`` values to the identity if the userid matches ``chris``
  401. or ``whit``.
  402. Known Plugins for :mod:`repoze.who`
  403. ===================================
  404. Plugins shipped with :mod:`repoze.who`
  405. --------------------------------------
  406. See :ref:`default_plugins`.
  407. Deprecated plugins
  408. ------------------
  409. The :mod:`repoze.who.deprecatedplugins` distribution bundles the following
  410. plugin implementations which were shipped with :mod:`repoze.who` prior
  411. to version 2.0a3. These plugins are deprecated, and should only be used
  412. while migrating an existing deployment to replacement versions.
  413. :class:`repoze.who.plugins.cookie.InsecureCookiePlugin`
  414. An ``IIdentifier`` plugin which stores identification information in an
  415. insecure form (the base64 value of the username and password separated by
  416. a colon) in a client-side cookie. Please use the
  417. :class:`AuthTktCookiePlugin` instead.
  418. :class:`repoze.who.plugins.form.FormPlugin`
  419. An ``IIdentifier`` and ``IChallenger`` plugin, which intercepts form POSTs
  420. to gather identification at ingress and conditionally displays a login form
  421. at egress if challenge is required.
  422. Applications should supply their
  423. own login form, and use :class:`repoze.who.api.API` to authenticate
  424. and remember users. To replace the challenger role, please use
  425. :class:`repoze.who.plugins.redirector.RedirectorPlugin`, configured with
  426. the URL of your application's login form.
  427. :class:`repoze.who.plugins.form.RedirectingFormPlugin`
  428. An ``IIdentifier`` and ``IChallenger`` plugin, which intercepts form POSTs
  429. to gather identification at ingress and conditionally redirects a login form
  430. at egress if challenge is required.
  431. Applications should supply their
  432. own login form, and use :class:`repoze.who.api.API` to authenticate
  433. and remember users. To replace the challenger role, please use
  434. :class:`repoze.who.plugins.redirector.RedirectorPlugin`, configured with
  435. the URL of your application's login form.
  436. Third-party Plugins
  437. -------------------
  438. :class:`repoze.who.plugins.zodb.ZODBPlugin`
  439. This class implements the :class:`repoze.who.interfaces.IAuthenticator`
  440. and :class:`repoze.who.interfaces.IMetadataProvider` plugin interfaces
  441. using ZODB database lookups. See
  442. http://pypi.python.org/pypi/repoze.whoplugins.zodb/
  443. :class:`repoze.who.plugins.ldap.LDAPAuthenticatorPlugin`
  444. This class implements the :class:`repoze.who.interfaces.IAuthenticator`
  445. plugin interface using the :mod:`python-ldap` library to query an LDAP
  446. database. See http://code.gustavonarea.net/repoze.who.plugins.ldap/
  447. :class:`repoze.who.plugins.ldap.LDAPAttributesPlugin`
  448. This class implements the :class:`repoze.who.interfaces.IMetadataProvider`
  449. plugin interface using the :mod:`python-ldap` library to query an LDAP
  450. database. See http://code.gustavonarea.net/repoze.who.plugins.ldap/
  451. :class:`repoze.who.plugins.friendlyform.FriendlyFormPlugin`
  452. This class implements the :class:`repoze.who.interfaces.IIdentifier` and
  453. :class:`repoze.who.interfaces.IChallenger` plugin interfaces. It is
  454. similar to :class:`repoze.who.plugins.form.RedirectingFormPlugin`,
  455. bt with with additional features:
  456. - Users are not challenged on logout, unless the referrer URL is a
  457. private one (but that’s up to the application).
  458. - Developers may define post-login and/or post-logout pages.
  459. - In the login URL, the amount of failed logins is available in the
  460. environ. It’s also increased by one on every login try. This counter
  461. will allow developers not using a post-login page to handle logins that
  462. fail/succeed.
  463. See http://code.gustavonarea.net/repoze.who-friendlyform/
  464. :func:`repoze.who.plugins.openid.identifiers.OpenIdIdentificationPlugin`
  465. This class implements the :class:`repoze.who.interfaces.IIdentifier`,
  466. :class:`repoze.who.interfaces.IAuthenticator`, and
  467. :class:`repoze.who.interfaces.IChallenger` plugin interfaces using OpenId.
  468. See http://quantumcore.org/docs/repoze.who.plugins.openid/
  469. :func:`repoze.who.plugins.openid.classifiers.openid_challenge_decider`
  470. This function provides the :class:`repoze.who.interfaces.IChallengeDecider`
  471. interface using OpenId. See
  472. http://quantumcore.org/docs/repoze.who.plugins.openid/
  473. :class:`repoze.who.plugins.use_beaker.UseBeakerPlugin`
  474. This packkage provids a :class:`repoze.who.interfaces.IIdentifier` plugin
  475. using :mod:`beaker.session` cache. See
  476. http://pypi.python.org/pypi/repoze.who-use_beaker/
  477. :class:`repoze.who.plugins.cas.main_plugin.CASChallengePlugin`
  478. This class implements the :class:`repoze.who.interfaces.IIdentifier`
  479. :class:`repoze.who.interfaces.IAuthenticator`, and
  480. :class:`repoze.who.interfaces.IChallenger` plugin interfaces using CAS.
  481. See http://pypi.python.org/pypi/repoze.who.plugins.cas
  482. :class:`repoze.who.plugins.cas.challenge_decider.my_challenge_decider`
  483. This function provides the :class:`repoze.who.interfaces.IChallengeDecider`
  484. interface using CAS. See
  485. http://pypi.python.org/pypi/repoze.who.plugins.cas/
  486. :class:`repoze.who.plugins.recaptcha.captcha.RecaptchaPlugin`
  487. This class implements the :class:`repoze.who.interfaces.IAuthenticator`
  488. plugin interface, using the recaptch API.
  489. See http://pypi.python.org/pypi/repoze.who.plugins.recaptcha/
  490. :class:`repoze.who.plugins.sa.SQLAlchemyUserChecker`
  491. User existence checker for
  492. :class:`repoze.who.plugins.auth_tkt.AuthTktCookiePlugin`, based on
  493. the SQLAlchemy ORM. See http://pypi.python.org/pypi/repoze.who.plugins.sa/
  494. :class:`repoze.who.plugins.sa.SQLAlchemyAuthenticatorPlugin`
  495. This class implements the :class:`repoze.who.interfaces.IAuthenticator`
  496. plugin interface, using the the SQLAlchemy ORM.
  497. See http://pypi.python.org/pypi/repoze.who.plugins.sa/
  498. :class:`repoze.who.plugins.sa.SQLAlchemyUserMDPlugin`
  499. This class implements the :class:`repoze.who.interfaces.IMetadataProvider`
  500. plugin interface, using the the SQLAlchemy ORM.
  501. See http://pypi.python.org/pypi/repoze.who.plugins.sa/
  502. :class:`repoze.who.plugins.formcookie.CookieRedirectingFormPlugin`
  503. This class implements the :class:`repoze.who.interfaces.IIdentifier` and
  504. :class:`repoze.who.interfaces.IChallenger` plugin interfaces, similar
  505. to :class:`repoze.who.plugins.form.RedirectingFormPlugin`. The
  506. plugin tracks the ``came_from`` URL via a cookie, rather than the query
  507. string. See http://pypi.python.org/pypi/repoze.who.plugins.formcookie/