Open Media Library Platform
This commit is contained in:
commit
411ad5b16f
5849 changed files with 1778641 additions and 0 deletions
13
Darwin/lib/python2.7/site-packages/twisted/cred/__init__.py
Normal file
13
Darwin/lib/python2.7/site-packages/twisted/cred/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""
|
||||
Twisted Cred
|
||||
|
||||
Support for verifying credentials, and providing services to users based on
|
||||
those credentials.
|
||||
|
||||
(This package was previously known as the module twisted.internet.passport.)
|
||||
"""
|
||||
129
Darwin/lib/python2.7/site-packages/twisted/cred/_digest.py
Normal file
129
Darwin/lib/python2.7/site-packages/twisted/cred/_digest.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# -*- test-case-name: twisted.test.test_digestauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Calculations for HTTP Digest authentication.
|
||||
|
||||
@see: U{http://www.faqs.org/rfcs/rfc2617.html}
|
||||
"""
|
||||
|
||||
from hashlib import md5, sha1
|
||||
|
||||
|
||||
|
||||
# The digest math
|
||||
|
||||
algorithms = {
|
||||
'md5': md5,
|
||||
|
||||
# md5-sess is more complicated than just another algorithm. It requires
|
||||
# H(A1) state to be remembered from the first WWW-Authenticate challenge
|
||||
# issued and re-used to process any Authorization header in response to
|
||||
# that WWW-Authenticate challenge. It is *not* correct to simply
|
||||
# recalculate H(A1) each time an Authorization header is received. Read
|
||||
# RFC 2617, section 3.2.2.2 and do not try to make DigestCredentialFactory
|
||||
# support this unless you completely understand it. -exarkun
|
||||
'md5-sess': md5,
|
||||
|
||||
'sha': sha1,
|
||||
}
|
||||
|
||||
# DigestCalcHA1
|
||||
def calcHA1(pszAlg, pszUserName, pszRealm, pszPassword, pszNonce, pszCNonce,
|
||||
preHA1=None):
|
||||
"""
|
||||
Compute H(A1) from RFC 2617.
|
||||
|
||||
@param pszAlg: The name of the algorithm to use to calculate the digest.
|
||||
Currently supported are md5, md5-sess, and sha.
|
||||
@param pszUserName: The username
|
||||
@param pszRealm: The realm
|
||||
@param pszPassword: The password
|
||||
@param pszNonce: The nonce
|
||||
@param pszCNonce: The cnonce
|
||||
|
||||
@param preHA1: If available this is a str containing a previously
|
||||
calculated H(A1) as a hex string. If this is given then the values for
|
||||
pszUserName, pszRealm, and pszPassword must be C{None} and are ignored.
|
||||
"""
|
||||
|
||||
if (preHA1 and (pszUserName or pszRealm or pszPassword)):
|
||||
raise TypeError(("preHA1 is incompatible with the pszUserName, "
|
||||
"pszRealm, and pszPassword arguments"))
|
||||
|
||||
if preHA1 is None:
|
||||
# We need to calculate the HA1 from the username:realm:password
|
||||
m = algorithms[pszAlg]()
|
||||
m.update(pszUserName)
|
||||
m.update(":")
|
||||
m.update(pszRealm)
|
||||
m.update(":")
|
||||
m.update(pszPassword)
|
||||
HA1 = m.digest()
|
||||
else:
|
||||
# We were given a username:realm:password
|
||||
HA1 = preHA1.decode('hex')
|
||||
|
||||
if pszAlg == "md5-sess":
|
||||
m = algorithms[pszAlg]()
|
||||
m.update(HA1)
|
||||
m.update(":")
|
||||
m.update(pszNonce)
|
||||
m.update(":")
|
||||
m.update(pszCNonce)
|
||||
HA1 = m.digest()
|
||||
|
||||
return HA1.encode('hex')
|
||||
|
||||
|
||||
def calcHA2(algo, pszMethod, pszDigestUri, pszQop, pszHEntity):
|
||||
"""
|
||||
Compute H(A2) from RFC 2617.
|
||||
|
||||
@param pszAlg: The name of the algorithm to use to calculate the digest.
|
||||
Currently supported are md5, md5-sess, and sha.
|
||||
@param pszMethod: The request method.
|
||||
@param pszDigestUri: The request URI.
|
||||
@param pszQop: The Quality-of-Protection value.
|
||||
@param pszHEntity: The hash of the entity body or C{None} if C{pszQop} is
|
||||
not C{'auth-int'}.
|
||||
@return: The hash of the A2 value for the calculation of the response
|
||||
digest.
|
||||
"""
|
||||
m = algorithms[algo]()
|
||||
m.update(pszMethod)
|
||||
m.update(":")
|
||||
m.update(pszDigestUri)
|
||||
if pszQop == "auth-int":
|
||||
m.update(":")
|
||||
m.update(pszHEntity)
|
||||
return m.digest().encode('hex')
|
||||
|
||||
|
||||
def calcResponse(HA1, HA2, algo, pszNonce, pszNonceCount, pszCNonce, pszQop):
|
||||
"""
|
||||
Compute the digest for the given parameters.
|
||||
|
||||
@param HA1: The H(A1) value, as computed by L{calcHA1}.
|
||||
@param HA2: The H(A2) value, as computed by L{calcHA2}.
|
||||
@param pszNonce: The challenge nonce.
|
||||
@param pszNonceCount: The (client) nonce count value for this response.
|
||||
@param pszCNonce: The client nonce.
|
||||
@param pszQop: The Quality-of-Protection value.
|
||||
"""
|
||||
m = algorithms[algo]()
|
||||
m.update(HA1)
|
||||
m.update(":")
|
||||
m.update(pszNonce)
|
||||
m.update(":")
|
||||
if pszNonceCount and pszCNonce:
|
||||
m.update(pszNonceCount)
|
||||
m.update(":")
|
||||
m.update(pszCNonce)
|
||||
m.update(":")
|
||||
m.update(pszQop)
|
||||
m.update(":")
|
||||
m.update(HA2)
|
||||
respHash = m.digest().encode('hex')
|
||||
return respHash
|
||||
268
Darwin/lib/python2.7/site-packages/twisted/cred/checkers.py
Normal file
268
Darwin/lib/python2.7/site-packages/twisted/cred/checkers.py
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
# -*- test-case-name: twisted.test.test_newcred -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
import os
|
||||
|
||||
from zope.interface import implements, Interface, Attribute
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python import failure, log
|
||||
from twisted.cred import error, credentials
|
||||
|
||||
|
||||
|
||||
class ICredentialsChecker(Interface):
|
||||
"""
|
||||
An object that can check sub-interfaces of ICredentials.
|
||||
"""
|
||||
|
||||
credentialInterfaces = Attribute(
|
||||
'A list of sub-interfaces of ICredentials which specifies which I may check.')
|
||||
|
||||
|
||||
def requestAvatarId(credentials):
|
||||
"""
|
||||
@param credentials: something which implements one of the interfaces in
|
||||
self.credentialInterfaces.
|
||||
|
||||
@return: a Deferred which will fire a string which identifies an
|
||||
avatar, an empty tuple to specify an authenticated anonymous user
|
||||
(provided as checkers.ANONYMOUS) or fire a Failure(UnauthorizedLogin).
|
||||
Alternatively, return the result itself.
|
||||
|
||||
@see: L{twisted.cred.credentials}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# A note on anonymity - We do not want None as the value for anonymous
|
||||
# because it is too easy to accidentally return it. We do not want the
|
||||
# empty string, because it is too easy to mistype a password file. For
|
||||
# example, an .htpasswd file may contain the lines: ['hello:asdf',
|
||||
# 'world:asdf', 'goodbye', ':world']. This misconfiguration will have an
|
||||
# ill effect in any case, but accidentally granting anonymous access is a
|
||||
# worse failure mode than simply granting access to an untypeable
|
||||
# username. We do not want an instance of 'object', because that would
|
||||
# create potential problems with persistence.
|
||||
|
||||
ANONYMOUS = ()
|
||||
|
||||
|
||||
class AllowAnonymousAccess:
|
||||
implements(ICredentialsChecker)
|
||||
credentialInterfaces = credentials.IAnonymous,
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
return defer.succeed(ANONYMOUS)
|
||||
|
||||
|
||||
class InMemoryUsernamePasswordDatabaseDontUse:
|
||||
"""
|
||||
An extremely simple credentials checker.
|
||||
|
||||
This is only of use in one-off test programs or examples which don't
|
||||
want to focus too much on how credentials are verified.
|
||||
|
||||
You really don't want to use this for anything else. It is, at best, a
|
||||
toy. If you need a simple credentials checker for a real application,
|
||||
see L{FilePasswordDB}.
|
||||
"""
|
||||
|
||||
implements(ICredentialsChecker)
|
||||
|
||||
credentialInterfaces = (credentials.IUsernamePassword,
|
||||
credentials.IUsernameHashedPassword)
|
||||
|
||||
def __init__(self, **users):
|
||||
self.users = users
|
||||
|
||||
def addUser(self, username, password):
|
||||
self.users[username] = password
|
||||
|
||||
def _cbPasswordMatch(self, matched, username):
|
||||
if matched:
|
||||
return username
|
||||
else:
|
||||
return failure.Failure(error.UnauthorizedLogin())
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
if credentials.username in self.users:
|
||||
return defer.maybeDeferred(
|
||||
credentials.checkPassword,
|
||||
self.users[credentials.username]).addCallback(
|
||||
self._cbPasswordMatch, str(credentials.username))
|
||||
else:
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
|
||||
|
||||
class FilePasswordDB:
|
||||
"""A file-based, text-based username/password database.
|
||||
|
||||
Records in the datafile for this class are delimited by a particular
|
||||
string. The username appears in a fixed field of the columns delimited
|
||||
by this string, as does the password. Both fields are specifiable. If
|
||||
the passwords are not stored plaintext, a hash function must be supplied
|
||||
to convert plaintext passwords to the form stored on disk and this
|
||||
CredentialsChecker will only be able to check IUsernamePassword
|
||||
credentials. If the passwords are stored plaintext,
|
||||
IUsernameHashedPassword credentials will be checkable as well.
|
||||
"""
|
||||
|
||||
implements(ICredentialsChecker)
|
||||
|
||||
cache = False
|
||||
_credCache = None
|
||||
_cacheTimestamp = 0
|
||||
|
||||
def __init__(self, filename, delim=':', usernameField=0, passwordField=1,
|
||||
caseSensitive=True, hash=None, cache=False):
|
||||
"""
|
||||
@type filename: C{str}
|
||||
@param filename: The name of the file from which to read username and
|
||||
password information.
|
||||
|
||||
@type delim: C{str}
|
||||
@param delim: The field delimiter used in the file.
|
||||
|
||||
@type usernameField: C{int}
|
||||
@param usernameField: The index of the username after splitting a
|
||||
line on the delimiter.
|
||||
|
||||
@type passwordField: C{int}
|
||||
@param passwordField: The index of the password after splitting a
|
||||
line on the delimiter.
|
||||
|
||||
@type caseSensitive: C{bool}
|
||||
@param caseSensitive: If true, consider the case of the username when
|
||||
performing a lookup. Ignore it otherwise.
|
||||
|
||||
@type hash: Three-argument callable or C{None}
|
||||
@param hash: A function used to transform the plaintext password
|
||||
received over the network to a format suitable for comparison
|
||||
against the version stored on disk. The arguments to the callable
|
||||
are the username, the network-supplied password, and the in-file
|
||||
version of the password. If the return value compares equal to the
|
||||
version stored on disk, the credentials are accepted.
|
||||
|
||||
@type cache: C{bool}
|
||||
@param cache: If true, maintain an in-memory cache of the
|
||||
contents of the password file. On lookups, the mtime of the
|
||||
file will be checked, and the file will only be re-parsed if
|
||||
the mtime is newer than when the cache was generated.
|
||||
"""
|
||||
self.filename = filename
|
||||
self.delim = delim
|
||||
self.ufield = usernameField
|
||||
self.pfield = passwordField
|
||||
self.caseSensitive = caseSensitive
|
||||
self.hash = hash
|
||||
self.cache = cache
|
||||
|
||||
if self.hash is None:
|
||||
# The passwords are stored plaintext. We can support both
|
||||
# plaintext and hashed passwords received over the network.
|
||||
self.credentialInterfaces = (
|
||||
credentials.IUsernamePassword,
|
||||
credentials.IUsernameHashedPassword
|
||||
)
|
||||
else:
|
||||
# The passwords are hashed on disk. We can support only
|
||||
# plaintext passwords received over the network.
|
||||
self.credentialInterfaces = (
|
||||
credentials.IUsernamePassword,
|
||||
)
|
||||
|
||||
|
||||
def __getstate__(self):
|
||||
d = dict(vars(self))
|
||||
for k in '_credCache', '_cacheTimestamp':
|
||||
try:
|
||||
del d[k]
|
||||
except KeyError:
|
||||
pass
|
||||
return d
|
||||
|
||||
|
||||
def _cbPasswordMatch(self, matched, username):
|
||||
if matched:
|
||||
return username
|
||||
else:
|
||||
return failure.Failure(error.UnauthorizedLogin())
|
||||
|
||||
|
||||
def _loadCredentials(self):
|
||||
try:
|
||||
f = file(self.filename)
|
||||
except:
|
||||
log.err()
|
||||
raise error.UnauthorizedLogin()
|
||||
else:
|
||||
for line in f:
|
||||
line = line.rstrip()
|
||||
parts = line.split(self.delim)
|
||||
|
||||
if self.ufield >= len(parts) or self.pfield >= len(parts):
|
||||
continue
|
||||
if self.caseSensitive:
|
||||
yield parts[self.ufield], parts[self.pfield]
|
||||
else:
|
||||
yield parts[self.ufield].lower(), parts[self.pfield]
|
||||
|
||||
|
||||
def getUser(self, username):
|
||||
if not self.caseSensitive:
|
||||
username = username.lower()
|
||||
|
||||
if self.cache:
|
||||
if self._credCache is None or os.path.getmtime(self.filename) > self._cacheTimestamp:
|
||||
self._cacheTimestamp = os.path.getmtime(self.filename)
|
||||
self._credCache = dict(self._loadCredentials())
|
||||
return username, self._credCache[username]
|
||||
else:
|
||||
for u, p in self._loadCredentials():
|
||||
if u == username:
|
||||
return u, p
|
||||
raise KeyError(username)
|
||||
|
||||
|
||||
def requestAvatarId(self, c):
|
||||
try:
|
||||
u, p = self.getUser(c.username)
|
||||
except KeyError:
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
else:
|
||||
up = credentials.IUsernamePassword(c, None)
|
||||
if self.hash:
|
||||
if up is not None:
|
||||
h = self.hash(up.username, up.password, p)
|
||||
if h == p:
|
||||
return defer.succeed(u)
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
else:
|
||||
return defer.maybeDeferred(c.checkPassword, p
|
||||
).addCallback(self._cbPasswordMatch, u)
|
||||
|
||||
|
||||
|
||||
class PluggableAuthenticationModulesChecker:
|
||||
implements(ICredentialsChecker)
|
||||
credentialInterfaces = credentials.IPluggableAuthenticationModules,
|
||||
service = 'Twisted'
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
try:
|
||||
from twisted.cred import pamauth
|
||||
except ImportError: # PyPAM is missing
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
else:
|
||||
d = pamauth.pamAuthenticate(self.service, credentials.username,
|
||||
credentials.pamConversion)
|
||||
d.addCallback(lambda x: credentials.username)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
# For backwards compatibility
|
||||
# Allow access as the old name.
|
||||
OnDiskUsernamePasswordDatabase = FilePasswordDB
|
||||
493
Darwin/lib/python2.7/site-packages/twisted/cred/credentials.py
Normal file
493
Darwin/lib/python2.7/site-packages/twisted/cred/credentials.py
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
# -*- test-case-name: twisted.test.test_newcred-*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
This module defines L{ICredentials}, an interface for objects that represent
|
||||
authentication credentials to provide, and also includes a number of useful
|
||||
implementations of that interface.
|
||||
"""
|
||||
|
||||
from zope.interface import implements, Interface
|
||||
|
||||
import hmac, time, random, re
|
||||
from hashlib import md5
|
||||
|
||||
from twisted.python.randbytes import secureRandom
|
||||
from twisted.cred._digest import calcResponse, calcHA1, calcHA2
|
||||
from twisted.cred import error
|
||||
|
||||
class ICredentials(Interface):
|
||||
"""
|
||||
I check credentials.
|
||||
|
||||
Implementors _must_ specify which sub-interfaces of ICredentials
|
||||
to which it conforms, using zope.interface.implements().
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class IUsernameDigestHash(ICredentials):
|
||||
"""
|
||||
This credential is used when a CredentialChecker has access to the hash
|
||||
of the username:realm:password as in an Apache .htdigest file.
|
||||
"""
|
||||
def checkHash(digestHash):
|
||||
"""
|
||||
@param digestHash: The hashed username:realm:password to check against.
|
||||
|
||||
@return: C{True} if the credentials represented by this object match
|
||||
the given hash, C{False} if they do not, or a L{Deferred} which
|
||||
will be called back with one of these values.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class IUsernameHashedPassword(ICredentials):
|
||||
"""
|
||||
I encapsulate a username and a hashed password.
|
||||
|
||||
This credential is used when a hashed password is received from the
|
||||
party requesting authentication. CredentialCheckers which check this
|
||||
kind of credential must store the passwords in plaintext (or as
|
||||
password-equivalent hashes) form so that they can be hashed in a manner
|
||||
appropriate for the particular credentials class.
|
||||
|
||||
@type username: C{str}
|
||||
@ivar username: The username associated with these credentials.
|
||||
"""
|
||||
|
||||
def checkPassword(password):
|
||||
"""
|
||||
Validate these credentials against the correct password.
|
||||
|
||||
@type password: C{str}
|
||||
@param password: The correct, plaintext password against which to
|
||||
check.
|
||||
|
||||
@rtype: C{bool} or L{Deferred}
|
||||
@return: C{True} if the credentials represented by this object match the
|
||||
given password, C{False} if they do not, or a L{Deferred} which will
|
||||
be called back with one of these values.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class IUsernamePassword(ICredentials):
|
||||
"""
|
||||
I encapsulate a username and a plaintext password.
|
||||
|
||||
This encapsulates the case where the password received over the network
|
||||
has been hashed with the identity function (That is, not at all). The
|
||||
CredentialsChecker may store the password in whatever format it desires,
|
||||
it need only transform the stored password in a similar way before
|
||||
performing the comparison.
|
||||
|
||||
@type username: C{str}
|
||||
@ivar username: The username associated with these credentials.
|
||||
|
||||
@type password: C{str}
|
||||
@ivar password: The password associated with these credentials.
|
||||
"""
|
||||
|
||||
def checkPassword(password):
|
||||
"""
|
||||
Validate these credentials against the correct password.
|
||||
|
||||
@type password: C{str}
|
||||
@param password: The correct, plaintext password against which to
|
||||
check.
|
||||
|
||||
@rtype: C{bool} or L{Deferred}
|
||||
@return: C{True} if the credentials represented by this object match the
|
||||
given password, C{False} if they do not, or a L{Deferred} which will
|
||||
be called back with one of these values.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class IAnonymous(ICredentials):
|
||||
"""
|
||||
I am an explicitly anonymous request for access.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class DigestedCredentials(object):
|
||||
"""
|
||||
Yet Another Simple HTTP Digest authentication scheme.
|
||||
"""
|
||||
implements(IUsernameHashedPassword, IUsernameDigestHash)
|
||||
|
||||
def __init__(self, username, method, realm, fields):
|
||||
self.username = username
|
||||
self.method = method
|
||||
self.realm = realm
|
||||
self.fields = fields
|
||||
|
||||
|
||||
def checkPassword(self, password):
|
||||
"""
|
||||
Verify that the credentials represented by this object agree with the
|
||||
given plaintext C{password} by hashing C{password} in the same way the
|
||||
response hash represented by this object was generated and comparing
|
||||
the results.
|
||||
"""
|
||||
response = self.fields.get('response')
|
||||
uri = self.fields.get('uri')
|
||||
nonce = self.fields.get('nonce')
|
||||
cnonce = self.fields.get('cnonce')
|
||||
nc = self.fields.get('nc')
|
||||
algo = self.fields.get('algorithm', 'md5').lower()
|
||||
qop = self.fields.get('qop', 'auth')
|
||||
|
||||
expected = calcResponse(
|
||||
calcHA1(algo, self.username, self.realm, password, nonce, cnonce),
|
||||
calcHA2(algo, self.method, uri, qop, None),
|
||||
algo, nonce, nc, cnonce, qop)
|
||||
|
||||
return expected == response
|
||||
|
||||
|
||||
def checkHash(self, digestHash):
|
||||
"""
|
||||
Verify that the credentials represented by this object agree with the
|
||||
credentials represented by the I{H(A1)} given in C{digestHash}.
|
||||
|
||||
@param digestHash: A precomputed H(A1) value based on the username,
|
||||
realm, and password associate with this credentials object.
|
||||
"""
|
||||
response = self.fields.get('response')
|
||||
uri = self.fields.get('uri')
|
||||
nonce = self.fields.get('nonce')
|
||||
cnonce = self.fields.get('cnonce')
|
||||
nc = self.fields.get('nc')
|
||||
algo = self.fields.get('algorithm', 'md5').lower()
|
||||
qop = self.fields.get('qop', 'auth')
|
||||
|
||||
expected = calcResponse(
|
||||
calcHA1(algo, None, None, None, nonce, cnonce, preHA1=digestHash),
|
||||
calcHA2(algo, self.method, uri, qop, None),
|
||||
algo, nonce, nc, cnonce, qop)
|
||||
|
||||
return expected == response
|
||||
|
||||
|
||||
|
||||
class DigestCredentialFactory(object):
|
||||
"""
|
||||
Support for RFC2617 HTTP Digest Authentication
|
||||
|
||||
@cvar CHALLENGE_LIFETIME_SECS: The number of seconds for which an
|
||||
opaque should be valid.
|
||||
|
||||
@type privateKey: C{str}
|
||||
@ivar privateKey: A random string used for generating the secure opaque.
|
||||
|
||||
@type algorithm: C{str}
|
||||
@param algorithm: Case insensitive string specifying the hash algorithm to
|
||||
use. Must be either C{'md5'} or C{'sha'}. C{'md5-sess'} is B{not}
|
||||
supported.
|
||||
|
||||
@type authenticationRealm: C{str}
|
||||
@param authenticationRealm: case sensitive string that specifies the realm
|
||||
portion of the challenge
|
||||
"""
|
||||
|
||||
_parseparts = re.compile(
|
||||
b'([^= ]+)' # The key
|
||||
b'=' # Conventional key/value separator (literal)
|
||||
b'(?:' # Group together a couple options
|
||||
b'"([^"]*)"' # A quoted string of length 0 or more
|
||||
b'|' # The other option in the group is coming
|
||||
b'([^,]+)' # An unquoted string of length 1 or more, up to a comma
|
||||
b')' # That non-matching group ends
|
||||
b',?') # There might be a comma at the end (none on last pair)
|
||||
|
||||
CHALLENGE_LIFETIME_SECS = 15 * 60 # 15 minutes
|
||||
|
||||
scheme = "digest"
|
||||
|
||||
def __init__(self, algorithm, authenticationRealm):
|
||||
self.algorithm = algorithm
|
||||
self.authenticationRealm = authenticationRealm
|
||||
self.privateKey = secureRandom(12)
|
||||
|
||||
|
||||
def getChallenge(self, address):
|
||||
"""
|
||||
Generate the challenge for use in the WWW-Authenticate header.
|
||||
|
||||
@param address: The client address to which this challenge is being
|
||||
sent.
|
||||
|
||||
@return: The C{dict} that can be used to generate a WWW-Authenticate
|
||||
header.
|
||||
"""
|
||||
c = self._generateNonce()
|
||||
o = self._generateOpaque(c, address)
|
||||
|
||||
return {'nonce': c,
|
||||
'opaque': o,
|
||||
'qop': 'auth',
|
||||
'algorithm': self.algorithm,
|
||||
'realm': self.authenticationRealm}
|
||||
|
||||
|
||||
def _generateNonce(self):
|
||||
"""
|
||||
Create a random value suitable for use as the nonce parameter of a
|
||||
WWW-Authenticate challenge.
|
||||
|
||||
@rtype: C{str}
|
||||
"""
|
||||
return secureRandom(12).encode('hex')
|
||||
|
||||
|
||||
def _getTime(self):
|
||||
"""
|
||||
Parameterize the time based seed used in C{_generateOpaque}
|
||||
so we can deterministically unittest it's behavior.
|
||||
"""
|
||||
return time.time()
|
||||
|
||||
|
||||
def _generateOpaque(self, nonce, clientip):
|
||||
"""
|
||||
Generate an opaque to be returned to the client. This is a unique
|
||||
string that can be returned to us and verified.
|
||||
"""
|
||||
# Now, what we do is encode the nonce, client ip and a timestamp in the
|
||||
# opaque value with a suitable digest.
|
||||
now = str(int(self._getTime()))
|
||||
if clientip is None:
|
||||
clientip = ''
|
||||
key = "%s,%s,%s" % (nonce, clientip, now)
|
||||
digest = md5(key + self.privateKey).hexdigest()
|
||||
ekey = key.encode('base64')
|
||||
return "%s-%s" % (digest, ekey.replace('\n', ''))
|
||||
|
||||
|
||||
def _verifyOpaque(self, opaque, nonce, clientip):
|
||||
"""
|
||||
Given the opaque and nonce from the request, as well as the client IP
|
||||
that made the request, verify that the opaque was generated by us.
|
||||
And that it's not too old.
|
||||
|
||||
@param opaque: The opaque value from the Digest response
|
||||
@param nonce: The nonce value from the Digest response
|
||||
@param clientip: The remote IP address of the client making the request
|
||||
or C{None} if the request was submitted over a channel where this
|
||||
does not make sense.
|
||||
|
||||
@return: C{True} if the opaque was successfully verified.
|
||||
|
||||
@raise error.LoginFailed: if C{opaque} could not be parsed or
|
||||
contained the wrong values.
|
||||
"""
|
||||
# First split the digest from the key
|
||||
opaqueParts = opaque.split('-')
|
||||
if len(opaqueParts) != 2:
|
||||
raise error.LoginFailed('Invalid response, invalid opaque value')
|
||||
|
||||
if clientip is None:
|
||||
clientip = ''
|
||||
|
||||
# Verify the key
|
||||
key = opaqueParts[1].decode('base64')
|
||||
keyParts = key.split(',')
|
||||
|
||||
if len(keyParts) != 3:
|
||||
raise error.LoginFailed('Invalid response, invalid opaque value')
|
||||
|
||||
if keyParts[0] != nonce:
|
||||
raise error.LoginFailed(
|
||||
'Invalid response, incompatible opaque/nonce values')
|
||||
|
||||
if keyParts[1] != clientip:
|
||||
raise error.LoginFailed(
|
||||
'Invalid response, incompatible opaque/client values')
|
||||
|
||||
try:
|
||||
when = int(keyParts[2])
|
||||
except ValueError:
|
||||
raise error.LoginFailed(
|
||||
'Invalid response, invalid opaque/time values')
|
||||
|
||||
if (int(self._getTime()) - when >
|
||||
DigestCredentialFactory.CHALLENGE_LIFETIME_SECS):
|
||||
|
||||
raise error.LoginFailed(
|
||||
'Invalid response, incompatible opaque/nonce too old')
|
||||
|
||||
# Verify the digest
|
||||
digest = md5(key + self.privateKey).hexdigest()
|
||||
if digest != opaqueParts[0]:
|
||||
raise error.LoginFailed('Invalid response, invalid opaque value')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def decode(self, response, method, host):
|
||||
"""
|
||||
Decode the given response and attempt to generate a
|
||||
L{DigestedCredentials} from it.
|
||||
|
||||
@type response: C{str}
|
||||
@param response: A string of comma seperated key=value pairs
|
||||
|
||||
@type method: C{str}
|
||||
@param method: The action requested to which this response is addressed
|
||||
(GET, POST, INVITE, OPTIONS, etc).
|
||||
|
||||
@type host: C{str}
|
||||
@param host: The address the request was sent from.
|
||||
|
||||
@raise error.LoginFailed: If the response does not contain a username,
|
||||
a nonce, an opaque, or if the opaque is invalid.
|
||||
|
||||
@return: L{DigestedCredentials}
|
||||
"""
|
||||
response = ' '.join(response.splitlines())
|
||||
parts = self._parseparts.findall(response)
|
||||
auth = {}
|
||||
for (key, bare, quoted) in parts:
|
||||
value = (quoted or bare).strip()
|
||||
auth[key.strip()] = value
|
||||
|
||||
username = auth.get('username')
|
||||
if not username:
|
||||
raise error.LoginFailed('Invalid response, no username given.')
|
||||
|
||||
if 'opaque' not in auth:
|
||||
raise error.LoginFailed('Invalid response, no opaque given.')
|
||||
|
||||
if 'nonce' not in auth:
|
||||
raise error.LoginFailed('Invalid response, no nonce given.')
|
||||
|
||||
# Now verify the nonce/opaque values for this client
|
||||
if self._verifyOpaque(auth.get('opaque'), auth.get('nonce'), host):
|
||||
return DigestedCredentials(username,
|
||||
method,
|
||||
self.authenticationRealm,
|
||||
auth)
|
||||
|
||||
|
||||
|
||||
class CramMD5Credentials:
|
||||
implements(IUsernameHashedPassword)
|
||||
|
||||
challenge = ''
|
||||
response = ''
|
||||
|
||||
def __init__(self, host=None):
|
||||
self.host = host
|
||||
|
||||
def getChallenge(self):
|
||||
if self.challenge:
|
||||
return self.challenge
|
||||
# The data encoded in the first ready response contains an
|
||||
# presumptively arbitrary string of random digits, a timestamp, and
|
||||
# the fully-qualified primary host name of the server. The syntax of
|
||||
# the unencoded form must correspond to that of an RFC 822 'msg-id'
|
||||
# [RFC822] as described in [POP3].
|
||||
# -- RFC 2195
|
||||
r = random.randrange(0x7fffffff)
|
||||
t = time.time()
|
||||
self.challenge = '<%d.%d@%s>' % (r, t, self.host)
|
||||
return self.challenge
|
||||
|
||||
def setResponse(self, response):
|
||||
self.username, self.response = response.split(None, 1)
|
||||
|
||||
def moreChallenges(self):
|
||||
return False
|
||||
|
||||
def checkPassword(self, password):
|
||||
verify = hmac.HMAC(password, self.challenge).hexdigest()
|
||||
return verify == self.response
|
||||
|
||||
|
||||
class UsernameHashedPassword:
|
||||
implements(IUsernameHashedPassword)
|
||||
|
||||
def __init__(self, username, hashed):
|
||||
self.username = username
|
||||
self.hashed = hashed
|
||||
|
||||
def checkPassword(self, password):
|
||||
return self.hashed == password
|
||||
|
||||
|
||||
class UsernamePassword:
|
||||
implements(IUsernamePassword)
|
||||
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def checkPassword(self, password):
|
||||
return self.password == password
|
||||
|
||||
|
||||
class Anonymous:
|
||||
implements(IAnonymous)
|
||||
|
||||
|
||||
|
||||
class ISSHPrivateKey(ICredentials):
|
||||
"""
|
||||
L{ISSHPrivateKey} credentials encapsulate an SSH public key to be checked
|
||||
against a user's private key.
|
||||
|
||||
@ivar username: The username associated with these credentials.
|
||||
@type username: C{str}
|
||||
|
||||
@ivar algName: The algorithm name for the blob.
|
||||
@type algName: C{str}
|
||||
|
||||
@ivar blob: The public key blob as sent by the client.
|
||||
@type blob: C{str}
|
||||
|
||||
@ivar sigData: The data the signature was made from.
|
||||
@type sigData: C{str}
|
||||
|
||||
@ivar signature: The signed data. This is checked to verify that the user
|
||||
owns the private key.
|
||||
@type signature: C{str} or C{NoneType}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class SSHPrivateKey:
|
||||
implements(ISSHPrivateKey)
|
||||
def __init__(self, username, algName, blob, sigData, signature):
|
||||
self.username = username
|
||||
self.algName = algName
|
||||
self.blob = blob
|
||||
self.sigData = sigData
|
||||
self.signature = signature
|
||||
|
||||
|
||||
class IPluggableAuthenticationModules(ICredentials):
|
||||
"""I encapsulate the authentication of a user via PAM (Pluggable
|
||||
Authentication Modules. I use PyPAM (available from
|
||||
http://www.tummy.com/Software/PyPam/index.html).
|
||||
|
||||
@ivar username: The username for the user being logged in.
|
||||
|
||||
@ivar pamConversion: A function that is called with a list of tuples
|
||||
(message, messageType). See the PAM documentation
|
||||
for the meaning of messageType. The function
|
||||
returns a Deferred which will fire with a list
|
||||
of (response, 0), one for each message. The 0 is
|
||||
currently unused, but is required by the PAM library.
|
||||
"""
|
||||
|
||||
class PluggableAuthenticationModules:
|
||||
implements(IPluggableAuthenticationModules)
|
||||
|
||||
def __init__(self, username, pamConversion):
|
||||
self.username = username
|
||||
self.pamConversion = pamConversion
|
||||
|
||||
41
Darwin/lib/python2.7/site-packages/twisted/cred/error.py
Normal file
41
Darwin/lib/python2.7/site-packages/twisted/cred/error.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""Cred errors."""
|
||||
|
||||
class Unauthorized(Exception):
|
||||
"""Standard unauthorized error."""
|
||||
|
||||
|
||||
|
||||
class LoginFailed(Exception):
|
||||
"""
|
||||
The user's request to log in failed for some reason.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class UnauthorizedLogin(LoginFailed, Unauthorized):
|
||||
"""The user was not authorized to log in.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class UnhandledCredentials(LoginFailed):
|
||||
"""A type of credentials were passed in with no knowledge of how to check
|
||||
them. This is a server configuration error - it means that a protocol was
|
||||
connected to a Portal without a CredentialChecker that can check all of its
|
||||
potential authentication strategies.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class LoginDenied(LoginFailed):
|
||||
"""
|
||||
The realm rejected this login for some reason.
|
||||
|
||||
Examples of reasons this might be raised include an avatar logging in
|
||||
too frequently, a quota having been fully used, or the overall server
|
||||
load being too high.
|
||||
"""
|
||||
79
Darwin/lib/python2.7/site-packages/twisted/cred/pamauth.py
Normal file
79
Darwin/lib/python2.7/site-packages/twisted/cred/pamauth.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Support for asynchronously authenticating using PAM.
|
||||
"""
|
||||
|
||||
|
||||
import PAM
|
||||
|
||||
import getpass, threading, os
|
||||
|
||||
from twisted.internet import threads, defer
|
||||
|
||||
def pamAuthenticateThread(service, user, conv):
|
||||
def _conv(items):
|
||||
from twisted.internet import reactor
|
||||
try:
|
||||
d = conv(items)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return
|
||||
ev = threading.Event()
|
||||
def cb(r):
|
||||
ev.r = (1, r)
|
||||
ev.set()
|
||||
def eb(e):
|
||||
ev.r = (0, e)
|
||||
ev.set()
|
||||
reactor.callFromThread(d.addCallbacks, cb, eb)
|
||||
ev.wait()
|
||||
done = ev.r
|
||||
if done[0]:
|
||||
return done[1]
|
||||
else:
|
||||
raise done[1].type, done[1].value
|
||||
|
||||
return callIntoPAM(service, user, _conv)
|
||||
|
||||
def callIntoPAM(service, user, conv):
|
||||
"""A testing hook.
|
||||
"""
|
||||
pam = PAM.pam()
|
||||
pam.start(service)
|
||||
pam.set_item(PAM.PAM_USER, user)
|
||||
pam.set_item(PAM.PAM_CONV, conv)
|
||||
gid = os.getegid()
|
||||
uid = os.geteuid()
|
||||
os.setegid(0)
|
||||
os.seteuid(0)
|
||||
try:
|
||||
pam.authenticate() # these will raise
|
||||
pam.acct_mgmt()
|
||||
return 1
|
||||
finally:
|
||||
os.setegid(gid)
|
||||
os.seteuid(uid)
|
||||
|
||||
def defConv(items):
|
||||
resp = []
|
||||
for i in range(len(items)):
|
||||
message, kind = items[i]
|
||||
if kind == 1: # password
|
||||
p = getpass.getpass(message)
|
||||
resp.append((p, 0))
|
||||
elif kind == 2: # text
|
||||
p = raw_input(message)
|
||||
resp.append((p, 0))
|
||||
elif kind in (3,4):
|
||||
print message
|
||||
resp.append(("", 0))
|
||||
else:
|
||||
return defer.fail('foo')
|
||||
d = defer.succeed(resp)
|
||||
return d
|
||||
|
||||
def pamAuthenticate(service, user, conv):
|
||||
return threads.deferToThread(pamAuthenticateThread, service, user, conv)
|
||||
121
Darwin/lib/python2.7/site-packages/twisted/cred/portal.py
Normal file
121
Darwin/lib/python2.7/site-packages/twisted/cred/portal.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# -*- test-case-name: twisted.test.test_newcred -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
The point of integration of application and authentication.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import maybeDeferred
|
||||
from twisted.python import failure, reflect
|
||||
from twisted.cred import error
|
||||
from zope.interface import providedBy, Interface
|
||||
|
||||
|
||||
class IRealm(Interface):
|
||||
"""
|
||||
The realm connects application-specific objects to the
|
||||
authentication system.
|
||||
"""
|
||||
def requestAvatar(avatarId, mind, *interfaces):
|
||||
"""
|
||||
Return avatar which provides one of the given interfaces.
|
||||
|
||||
@param avatarId: a string that identifies an avatar, as returned by
|
||||
L{ICredentialsChecker.requestAvatarId<twisted.cred.checkers.ICredentialsChecker.requestAvatarId>}
|
||||
(via a Deferred). Alternatively, it may be
|
||||
C{twisted.cred.checkers.ANONYMOUS}.
|
||||
@param mind: usually None. See the description of mind in
|
||||
L{Portal.login}.
|
||||
@param interfaces: the interface(s) the returned avatar should
|
||||
implement, e.g. C{IMailAccount}. See the description of
|
||||
L{Portal.login}.
|
||||
|
||||
@returns: a deferred which will fire a tuple of (interface,
|
||||
avatarAspect, logout), or the tuple itself. The interface will be
|
||||
one of the interfaces passed in the 'interfaces' argument. The
|
||||
'avatarAspect' will implement that interface. The 'logout' object
|
||||
is a callable which will detach the mind from the avatar.
|
||||
"""
|
||||
|
||||
|
||||
class Portal:
|
||||
"""
|
||||
A mediator between clients and a realm.
|
||||
|
||||
A portal is associated with one Realm and zero or more credentials checkers.
|
||||
When a login is attempted, the portal finds the appropriate credentials
|
||||
checker for the credentials given, invokes it, and if the credentials are
|
||||
valid, retrieves the appropriate avatar from the Realm.
|
||||
|
||||
This class is not intended to be subclassed. Customization should be done
|
||||
in the realm object and in the credentials checker objects.
|
||||
"""
|
||||
def __init__(self, realm, checkers=()):
|
||||
"""
|
||||
Create a Portal to a L{IRealm}.
|
||||
"""
|
||||
self.realm = realm
|
||||
self.checkers = {}
|
||||
for checker in checkers:
|
||||
self.registerChecker(checker)
|
||||
|
||||
def listCredentialsInterfaces(self):
|
||||
"""
|
||||
Return list of credentials interfaces that can be used to login.
|
||||
"""
|
||||
return self.checkers.keys()
|
||||
|
||||
def registerChecker(self, checker, *credentialInterfaces):
|
||||
if not credentialInterfaces:
|
||||
credentialInterfaces = checker.credentialInterfaces
|
||||
for credentialInterface in credentialInterfaces:
|
||||
self.checkers[credentialInterface] = checker
|
||||
|
||||
def login(self, credentials, mind, *interfaces):
|
||||
"""
|
||||
@param credentials: an implementor of
|
||||
L{twisted.cred.credentials.ICredentials}
|
||||
|
||||
@param mind: an object which implements a client-side interface for
|
||||
your particular realm. In many cases, this may be None, so if the
|
||||
word 'mind' confuses you, just ignore it.
|
||||
|
||||
@param interfaces: list of interfaces for the perspective that the mind
|
||||
wishes to attach to. Usually, this will be only one interface, for
|
||||
example IMailAccount. For highly dynamic protocols, however, this
|
||||
may be a list like (IMailAccount, IUserChooser, IServiceInfo). To
|
||||
expand: if we are speaking to the system over IMAP, any information
|
||||
that will be relayed to the user MUST be returned as an
|
||||
IMailAccount implementor; IMAP clients would not be able to
|
||||
understand anything else. Any information about unusual status
|
||||
would have to be relayed as a single mail message in an
|
||||
otherwise-empty mailbox. However, in a web-based mail system, or a
|
||||
PB-based client, the ``mind'' object inside the web server
|
||||
(implemented with a dynamic page-viewing mechanism such as a
|
||||
Twisted Web Resource) or on the user's client program may be
|
||||
intelligent enough to respond to several ``server''-side
|
||||
interfaces.
|
||||
|
||||
@return: A deferred which will fire a tuple of (interface,
|
||||
avatarAspect, logout). The interface will be one of the interfaces
|
||||
passed in the 'interfaces' argument. The 'avatarAspect' will
|
||||
implement that interface. The 'logout' object is a callable which
|
||||
will detach the mind from the avatar. It must be called when the
|
||||
user has conceptually disconnected from the service. Although in
|
||||
some cases this will not be in connectionLost (such as in a
|
||||
web-based session), it will always be at the end of a user's
|
||||
interactive session.
|
||||
"""
|
||||
for i in self.checkers:
|
||||
if i.providedBy(credentials):
|
||||
return maybeDeferred(self.checkers[i].requestAvatarId, credentials
|
||||
).addCallback(self.realm.requestAvatar, mind, *interfaces
|
||||
)
|
||||
ifac = providedBy(credentials)
|
||||
return defer.fail(failure.Failure(error.UnhandledCredentials(
|
||||
"No checker for %s" % ', '.join(map(reflect.qual, ifac)))))
|
||||
|
||||
270
Darwin/lib/python2.7/site-packages/twisted/cred/strcred.py
Normal file
270
Darwin/lib/python2.7/site-packages/twisted/cred/strcred.py
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
# -*- test-case-name: twisted.test.test_strcred -*-
|
||||
#
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
#
|
||||
|
||||
"""
|
||||
Support for resolving command-line strings that represent different
|
||||
checkers available to cred.
|
||||
|
||||
Examples:
|
||||
- passwd:/etc/passwd
|
||||
- memory:admin:asdf:user:lkj
|
||||
- unix
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
|
||||
from twisted.plugin import getPlugins
|
||||
from twisted.python import usage
|
||||
|
||||
|
||||
|
||||
class ICheckerFactory(Interface):
|
||||
"""
|
||||
A factory for objects which provide
|
||||
L{twisted.cred.checkers.ICredentialsChecker}.
|
||||
|
||||
It's implemented by twistd plugins creating checkers.
|
||||
"""
|
||||
|
||||
authType = Attribute(
|
||||
'A tag that identifies the authentication method.')
|
||||
|
||||
|
||||
authHelp = Attribute(
|
||||
'A detailed (potentially multi-line) description of precisely '
|
||||
'what functionality this CheckerFactory provides.')
|
||||
|
||||
|
||||
argStringFormat = Attribute(
|
||||
'A short (one-line) description of the argument string format.')
|
||||
|
||||
|
||||
credentialInterfaces = Attribute(
|
||||
'A list of credentials interfaces that this factory will support.')
|
||||
|
||||
|
||||
def generateChecker(argstring):
|
||||
"""
|
||||
Return an L{ICredentialChecker} provider using the supplied
|
||||
argument string.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class StrcredException(Exception):
|
||||
"""
|
||||
Base exception class for strcred.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class InvalidAuthType(StrcredException):
|
||||
"""
|
||||
Raised when a user provides an invalid identifier for the
|
||||
authentication plugin (known as the authType).
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class InvalidAuthArgumentString(StrcredException):
|
||||
"""
|
||||
Raised by an authentication plugin when the argument string
|
||||
provided is formatted incorrectly.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class UnsupportedInterfaces(StrcredException):
|
||||
"""
|
||||
Raised when an application is given a checker to use that does not
|
||||
provide any of the application's supported credentials interfaces.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# This will be used to warn the users whenever they view help for an
|
||||
# authType that is not supported by the application.
|
||||
notSupportedWarning = ("WARNING: This authType is not supported by "
|
||||
"this application.")
|
||||
|
||||
|
||||
|
||||
def findCheckerFactories():
|
||||
"""
|
||||
Find all objects that implement L{ICheckerFactory}.
|
||||
"""
|
||||
return getPlugins(ICheckerFactory)
|
||||
|
||||
|
||||
|
||||
def findCheckerFactory(authType):
|
||||
"""
|
||||
Find the first checker factory that supports the given authType.
|
||||
"""
|
||||
for factory in findCheckerFactories():
|
||||
if factory.authType == authType:
|
||||
return factory
|
||||
raise InvalidAuthType(authType)
|
||||
|
||||
|
||||
|
||||
def makeChecker(description):
|
||||
"""
|
||||
Returns an L{twisted.cred.checkers.ICredentialsChecker} based on the
|
||||
contents of a descriptive string. Similar to
|
||||
L{twisted.application.strports}.
|
||||
"""
|
||||
if ':' in description:
|
||||
authType, argstring = description.split(':', 1)
|
||||
else:
|
||||
authType = description
|
||||
argstring = ''
|
||||
return findCheckerFactory(authType).generateChecker(argstring)
|
||||
|
||||
|
||||
|
||||
class AuthOptionMixin:
|
||||
"""
|
||||
Defines helper methods that can be added on to any
|
||||
L{usage.Options} subclass that needs authentication.
|
||||
|
||||
This mixin implements three new options methods:
|
||||
|
||||
The opt_auth method (--auth) will write two new values to the
|
||||
'self' dictionary: C{credInterfaces} (a dict of lists) and
|
||||
C{credCheckers} (a list).
|
||||
|
||||
The opt_help_auth method (--help-auth) will search for all
|
||||
available checker plugins and list them for the user; it will exit
|
||||
when finished.
|
||||
|
||||
The opt_help_auth_type method (--help-auth-type) will display
|
||||
detailed help for a particular checker plugin.
|
||||
|
||||
@cvar supportedInterfaces: An iterable object that returns
|
||||
credential interfaces which this application is able to support.
|
||||
|
||||
@cvar authOutput: A writeable object to which this options class
|
||||
will send all help-related output. Default: L{sys.stdout}
|
||||
"""
|
||||
|
||||
supportedInterfaces = None
|
||||
authOutput = sys.stdout
|
||||
|
||||
|
||||
def supportsInterface(self, interface):
|
||||
"""
|
||||
Returns whether a particular credentials interface is supported.
|
||||
"""
|
||||
return (self.supportedInterfaces is None
|
||||
or interface in self.supportedInterfaces)
|
||||
|
||||
|
||||
def supportsCheckerFactory(self, factory):
|
||||
"""
|
||||
Returns whether a checker factory will provide at least one of
|
||||
the credentials interfaces that we care about.
|
||||
"""
|
||||
for interface in factory.credentialInterfaces:
|
||||
if self.supportsInterface(interface):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def addChecker(self, checker):
|
||||
"""
|
||||
Supply a supplied credentials checker to the Options class.
|
||||
"""
|
||||
# First figure out which interfaces we're willing to support.
|
||||
supported = []
|
||||
if self.supportedInterfaces is None:
|
||||
supported = checker.credentialInterfaces
|
||||
else:
|
||||
for interface in checker.credentialInterfaces:
|
||||
if self.supportsInterface(interface):
|
||||
supported.append(interface)
|
||||
if not supported:
|
||||
raise UnsupportedInterfaces(checker.credentialInterfaces)
|
||||
# If we get this far, then we know we can use this checker.
|
||||
if 'credInterfaces' not in self:
|
||||
self['credInterfaces'] = {}
|
||||
if 'credCheckers' not in self:
|
||||
self['credCheckers'] = []
|
||||
self['credCheckers'].append(checker)
|
||||
for interface in supported:
|
||||
self['credInterfaces'].setdefault(interface, []).append(checker)
|
||||
|
||||
|
||||
def opt_auth(self, description):
|
||||
"""
|
||||
Specify an authentication method for the server.
|
||||
"""
|
||||
try:
|
||||
self.addChecker(makeChecker(description))
|
||||
except UnsupportedInterfaces, e:
|
||||
raise usage.UsageError(
|
||||
'Auth plugin not supported: %s' % e.args[0])
|
||||
except InvalidAuthType, e:
|
||||
raise usage.UsageError(
|
||||
'Auth plugin not recognized: %s' % e.args[0])
|
||||
except Exception, e:
|
||||
raise usage.UsageError('Unexpected error: %s' % e)
|
||||
|
||||
|
||||
def _checkerFactoriesForOptHelpAuth(self):
|
||||
"""
|
||||
Return a list of which authTypes will be displayed by --help-auth.
|
||||
This makes it a lot easier to test this module.
|
||||
"""
|
||||
for factory in findCheckerFactories():
|
||||
for interface in factory.credentialInterfaces:
|
||||
if self.supportsInterface(interface):
|
||||
yield factory
|
||||
break
|
||||
|
||||
|
||||
def opt_help_auth(self):
|
||||
"""
|
||||
Show all authentication methods available.
|
||||
"""
|
||||
self.authOutput.write("Usage: --auth AuthType[:ArgString]\n")
|
||||
self.authOutput.write("For detailed help: --help-auth-type AuthType\n")
|
||||
self.authOutput.write('\n')
|
||||
# Figure out the right width for our columns
|
||||
firstLength = 0
|
||||
for factory in self._checkerFactoriesForOptHelpAuth():
|
||||
if len(factory.authType) > firstLength:
|
||||
firstLength = len(factory.authType)
|
||||
formatString = ' %%-%is\t%%s\n' % firstLength
|
||||
self.authOutput.write(formatString % ('AuthType', 'ArgString format'))
|
||||
self.authOutput.write(formatString % ('========', '================'))
|
||||
for factory in self._checkerFactoriesForOptHelpAuth():
|
||||
self.authOutput.write(
|
||||
formatString % (factory.authType, factory.argStringFormat))
|
||||
self.authOutput.write('\n')
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def opt_help_auth_type(self, authType):
|
||||
"""
|
||||
Show help for a particular authentication type.
|
||||
"""
|
||||
try:
|
||||
cf = findCheckerFactory(authType)
|
||||
except InvalidAuthType:
|
||||
raise usage.UsageError("Invalid auth type: %s" % authType)
|
||||
self.authOutput.write("Usage: --auth %s[:ArgString]\n" % authType)
|
||||
self.authOutput.write("ArgString format: %s\n" % cf.argStringFormat)
|
||||
self.authOutput.write('\n')
|
||||
for line in cf.authHelp.strip().splitlines():
|
||||
self.authOutput.write(' %s\n' % line.rstrip())
|
||||
self.authOutput.write('\n')
|
||||
if not self.supportsCheckerFactory(cf):
|
||||
self.authOutput.write(' %s\n' % notSupportedWarning)
|
||||
self.authOutput.write('\n')
|
||||
raise SystemExit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue