aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/cred
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/cred
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/cred')
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/__init__.py7
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/_digest.py132
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/checkers.py329
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/credentials.py510
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/error.py45
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/portal.py124
-rw-r--r--contrib/python/Twisted/py2/twisted/cred/strcred.py272
7 files changed, 1419 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/cred/__init__.py b/contrib/python/Twisted/py2/twisted/cred/__init__.py
new file mode 100644
index 0000000000..2ee268c5e9
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Twisted Cred: Support for verifying credentials, and providing services to user
+based on those credentials.
+"""
diff --git a/contrib/python/Twisted/py2/twisted/cred/_digest.py b/contrib/python/Twisted/py2/twisted/cred/_digest.py
new file mode 100644
index 0000000000..4ed1ce317a
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/_digest.py
@@ -0,0 +1,132 @@
+# -*- test-case-name: twisted.cred.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 __future__ import division, absolute_import
+
+from binascii import hexlify
+from hashlib import md5, sha1
+
+
+
+# The digest math
+
+algorithms = {
+ b'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
+ b'md5-sess': md5,
+
+ b'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 L{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(b":")
+ m.update(pszRealm)
+ m.update(b":")
+ m.update(pszPassword)
+ HA1 = hexlify(m.digest())
+ else:
+ # We were given a username:realm:password
+ HA1 = preHA1
+
+ if pszAlg == b"md5-sess":
+ m = algorithms[pszAlg]()
+ m.update(HA1)
+ m.update(b":")
+ m.update(pszNonce)
+ m.update(b":")
+ m.update(pszCNonce)
+ HA1 = hexlify(m.digest())
+
+ return HA1
+
+
+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 L{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(b":")
+ m.update(pszDigestUri)
+ if pszQop == b"auth-int":
+ m.update(b":")
+ m.update(pszHEntity)
+ return hexlify(m.digest())
+
+
+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(b":")
+ m.update(pszNonce)
+ m.update(b":")
+ if pszNonceCount and pszCNonce:
+ m.update(pszNonceCount)
+ m.update(b":")
+ m.update(pszCNonce)
+ m.update(b":")
+ m.update(pszQop)
+ m.update(b":")
+ m.update(HA2)
+ respHash = hexlify(m.digest())
+ return respHash
diff --git a/contrib/python/Twisted/py2/twisted/cred/checkers.py b/contrib/python/Twisted/py2/twisted/cred/checkers.py
new file mode 100644
index 0000000000..7a29809cb6
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/checkers.py
@@ -0,0 +1,329 @@
+# -*- test-case-name: twisted.cred.test.test_cred -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Basic credential checkers
+
+@var ANONYMOUS: An empty tuple used to represent the anonymous avatar ID.
+"""
+
+from __future__ import division, absolute_import
+
+import os
+
+from zope.interface import implementer, Interface, Attribute
+
+from twisted.logger import Logger
+from twisted.internet import defer
+from twisted.python import failure
+from twisted.cred import error, credentials
+
+
+
+class ICredentialsChecker(Interface):
+ """
+ An object that can check sub-interfaces of L{ICredentials}.
+ """
+
+ credentialInterfaces = Attribute((
+ 'A list of sub-interfaces of L{ICredentials} which specifies which I '
+ 'may check.'
+ ))
+
+
+ def requestAvatarId(credentials):
+ """
+ Validate credentials and produce an avatar ID.
+
+ @param credentials: something which implements one of the interfaces in
+ C{credentialInterfaces}.
+
+ @return: a L{Deferred} which will fire with a L{bytes} that identifies
+ an avatar, an empty tuple to specify an authenticated anonymous user
+ (provided as L{twisted.cred.checkers.ANONYMOUS}) or fail with
+ L{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 = ()
+
+
+
+@implementer(ICredentialsChecker)
+class AllowAnonymousAccess:
+ """
+ A credentials checker that unconditionally grants anonymous access.
+
+ @cvar credentialInterfaces: Tuple containing L{IAnonymous}.
+ """
+ credentialInterfaces = credentials.IAnonymous,
+
+ def requestAvatarId(self, credentials):
+ """
+ Succeed with the L{ANONYMOUS} avatar ID.
+
+ @return: L{Deferred} that fires with L{twisted.cred.checkers.ANONYMOUS}
+ """
+ return defer.succeed(ANONYMOUS)
+
+
+
+@implementer(ICredentialsChecker)
+class InMemoryUsernamePasswordDatabaseDontUse(object):
+ """
+ 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}.
+
+ @cvar credentialInterfaces: Tuple of L{IUsernamePassword} and
+ L{IUsernameHashedPassword}.
+
+ @ivar users: Mapping of usernames to passwords.
+ @type users: L{dict} mapping L{bytes} to L{bytes}
+ """
+ credentialInterfaces = (credentials.IUsernamePassword,
+ credentials.IUsernameHashedPassword)
+
+ def __init__(self, **users):
+ """
+ Initialize the in-memory database.
+
+ For example::
+
+ db = InMemoryUsernamePasswordDatabaseDontUse(
+ user1=b'sesame',
+ user2=b'hunter2',
+ )
+
+ @param users: Usernames and passwords to seed the database with.
+ Each username given as a keyword is encoded to L{bytes} as ASCII.
+ Passwords must be given as L{bytes}.
+ @type users: L{dict} of L{str} to L{bytes}
+ """
+ self.users = {x.encode('ascii'): y for x, y in users.items()}
+
+
+ def addUser(self, username, password):
+ """
+ Set a user's password.
+
+ @param username: Name of the user.
+ @type username: L{bytes}
+
+ @param password: Password to associate with the username.
+ @type password: L{bytes}
+ """
+ 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, credentials.username)
+ else:
+ return defer.fail(error.UnauthorizedLogin())
+
+
+
+@implementer(ICredentialsChecker)
+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 L{IUsernamePassword}
+ credentials. If the passwords are stored plaintext,
+ L{IUsernameHashedPassword} credentials will be checkable as well.
+ """
+
+ cache = False
+ _credCache = None
+ _cacheTimestamp = 0
+ _log = Logger()
+
+ def __init__(self, filename, delim=b':', usernameField=0, passwordField=1,
+ caseSensitive=True, hash=None, cache=False):
+ """
+ @type filename: L{str}
+ @param filename: The name of the file from which to read username and
+ password information.
+
+ @type delim: L{bytes}
+ @param delim: The field delimiter used in the file.
+
+ @type usernameField: L{int}
+ @param usernameField: The index of the username after splitting a
+ line on the delimiter.
+
+ @type passwordField: L{int}
+ @param passwordField: The index of the password after splitting a
+ line on the delimiter.
+
+ @type caseSensitive: L{bool}
+ @param caseSensitive: If true, consider the case of the username when
+ performing a lookup. Ignore it otherwise.
+
+ @type hash: Three-argument callable or L{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: L{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):
+ """
+ Loads the credentials from the configured file.
+
+ @return: An iterable of C{username, password} couples.
+ @rtype: C{iterable}
+
+ @raise UnauthorizedLogin: when failing to read the credentials from the
+ file.
+ """
+ try:
+ with open(self.filename, "rb") as f:
+ 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]
+ except IOError as e:
+ self._log.error("Unable to load credentials db: {e!r}", e=e)
+ raise error.UnauthorizedLogin()
+
+
+ def getUser(self, username):
+ """
+ Look up the credentials for a username.
+
+ @param username: The username to look up.
+ @type username: L{bytes}
+
+ @returns: Two-tuple of the canonicalicalized username (i.e. lowercase
+ if the database is not case sensitive) and the associated password
+ value, both L{bytes}.
+ @rtype: L{tuple}
+
+ @raises KeyError: When lookup of the username fails.
+ """
+ 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)
+
+
+
+# For backwards compatibility
+# Allow access as the old name.
+OnDiskUsernamePasswordDatabase = FilePasswordDB
diff --git a/contrib/python/Twisted/py2/twisted/cred/credentials.py b/contrib/python/Twisted/py2/twisted/cred/credentials.py
new file mode 100644
index 0000000000..5469e51587
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/credentials.py
@@ -0,0 +1,510 @@
+# -*- test-case-name: twisted.cred.test.test_cred-*-
+# 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 __future__ import division, absolute_import
+
+from zope.interface import implementer, Interface
+
+import base64
+import hmac
+import random
+import re
+import time
+
+from binascii import hexlify
+from hashlib import md5
+
+from twisted.python.randbytes import secureRandom
+from twisted.python.compat import networkString, nativeString
+from twisted.python.compat import intToBytes, unicode
+from twisted.cred._digest import calcResponse, calcHA1, calcHA2
+from twisted.cred import error
+
+
+
+class ICredentials(Interface):
+ """
+ I check credentials.
+
+ Implementors I{must} specify the sub-interfaces of ICredentials
+ to which it conforms, using L{zope.interface.declarations.implementer}.
+ """
+
+
+
+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: L{bytes}
+ @ivar username: The username associated with these credentials.
+ """
+
+ def checkPassword(password):
+ """
+ Validate these credentials against the correct password.
+
+ @type password: L{bytes}
+ @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: L{bytes}
+ @ivar username: The username associated with these credentials.
+
+ @type password: L{bytes}
+ @ivar password: The password associated with these credentials.
+ """
+
+ def checkPassword(password):
+ """
+ Validate these credentials against the correct password.
+
+ @type password: L{bytes}
+ @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.
+
+ @see: L{twisted.cred.checkers.AllowAnonymousAccess}
+ """
+
+
+
+@implementer(IUsernameHashedPassword, IUsernameDigestHash)
+class DigestedCredentials(object):
+ """
+ Yet Another Simple HTTP Digest authentication scheme.
+ """
+
+ 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', b'md5').lower()
+ qop = self.fields.get('qop', b'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', b'md5').lower()
+ qop = self.fields.get('qop', b'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: L{bytes}
+ @ivar privateKey: A random string used for generating the secure opaque.
+
+ @type algorithm: L{bytes}
+ @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: L{bytes}
+ @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 = b"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 L{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': b'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: L{bytes}
+ """
+ return hexlify(secureRandom(12))
+
+
+ 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 = intToBytes(int(self._getTime()))
+
+ if not clientip:
+ clientip = b''
+ elif isinstance(clientip, unicode):
+ clientip = clientip.encode('ascii')
+
+ key = b",".join((nonce, clientip, now))
+ digest = hexlify(md5(key + self.privateKey).digest())
+ ekey = base64.b64encode(key)
+ return b"-".join((digest, ekey.replace(b'\n', b'')))
+
+
+ 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 L{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(b'-')
+ if len(opaqueParts) != 2:
+ raise error.LoginFailed('Invalid response, invalid opaque value')
+
+ if not clientip:
+ clientip = b''
+ elif isinstance(clientip, unicode):
+ clientip = clientip.encode('ascii')
+
+ # Verify the key
+ key = base64.b64decode(opaqueParts[1])
+ keyParts = key.split(b',')
+
+ 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 = hexlify(md5(key + self.privateKey).digest())
+ 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: L{bytes}
+ @param response: A string of comma separated key=value pairs
+
+ @type method: L{bytes}
+ @param method: The action requested to which this response is addressed
+ (GET, POST, INVITE, OPTIONS, etc).
+
+ @type host: L{bytes}
+ @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 = b' '.join(response.splitlines())
+ parts = self._parseparts.findall(response)
+ auth = {}
+ for (key, bare, quoted) in parts:
+ value = (quoted or bare).strip()
+ auth[nativeString(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)
+
+
+
+@implementer(IUsernameHashedPassword)
+class CramMD5Credentials(object):
+ """
+ An encapsulation of some CramMD5 hashed credentials.
+
+ @ivar challenge: The challenge to be sent to the client.
+ @type challenge: L{bytes}
+
+ @ivar response: The hashed response from the client.
+ @type response: L{bytes}
+
+ @ivar username: The username from the response from the client.
+ @type username: L{bytes} or L{None} if not yet provided.
+ """
+ username = None
+ challenge = b''
+ response = b''
+
+ 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 = networkString('<%d.%d@%s>' % (
+ r, t, nativeString(self.host) if self.host else None))
+ 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 = hexlify(hmac.HMAC(password, self.challenge).digest())
+ return verify == self.response
+
+
+
+@implementer(IUsernameHashedPassword)
+class UsernameHashedPassword:
+
+ def __init__(self, username, hashed):
+ self.username = username
+ self.hashed = hashed
+
+ def checkPassword(self, password):
+ return self.hashed == password
+
+
+
+@implementer(IUsernamePassword)
+class UsernamePassword:
+
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
+
+ def checkPassword(self, password):
+ return self.password == password
+
+
+
+@implementer(IAnonymous)
+class Anonymous:
+ pass
+
+
+
+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: L{bytes}
+
+ @ivar algName: The algorithm name for the blob.
+ @type algName: L{bytes}
+
+ @ivar blob: The public key blob as sent by the client.
+ @type blob: L{bytes}
+
+ @ivar sigData: The data the signature was made from.
+ @type sigData: L{bytes}
+
+ @ivar signature: The signed data. This is checked to verify that the user
+ owns the private key.
+ @type signature: L{bytes} or L{None}
+ """
+
+
+
+@implementer(ISSHPrivateKey)
+class SSHPrivateKey:
+ def __init__(self, username, algName, blob, sigData, signature):
+ self.username = username
+ self.algName = algName
+ self.blob = blob
+ self.sigData = sigData
+ self.signature = signature
diff --git a/contrib/python/Twisted/py2/twisted/cred/error.py b/contrib/python/Twisted/py2/twisted/cred/error.py
new file mode 100644
index 0000000000..efd3ec3426
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/error.py
@@ -0,0 +1,45 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Cred errors.
+"""
+
+from __future__ import division, absolute_import
+
+
+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.
+ """
diff --git a/contrib/python/Twisted/py2/twisted/cred/portal.py b/contrib/python/Twisted/py2/twisted/cred/portal.py
new file mode 100644
index 0000000000..4c81d5f12c
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/portal.py
@@ -0,0 +1,124 @@
+# -*- test-case-name: twisted.cred.test.test_cred -*-
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+The point of integration of application and authentication.
+"""
+
+from __future__ import division, absolute_import
+
+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(object):
+ """
+ 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 list(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)))))
diff --git a/contrib/python/Twisted/py2/twisted/cred/strcred.py b/contrib/python/Twisted/py2/twisted/cred/strcred.py
new file mode 100644
index 0000000000..4c3e5bc95e
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/cred/strcred.py
@@ -0,0 +1,272 @@
+# -*- test-case-name: twisted.cred.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
+"""
+
+from __future__ import absolute_import, division
+
+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{twisted.cred.checkers.ICredentialsChecker} 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 as e:
+ raise usage.UsageError(
+ 'Auth plugin not supported: %s' % e.args[0])
+ except InvalidAuthType as e:
+ raise usage.UsageError(
+ 'Auth plugin not recognized: %s' % e.args[0])
+ except Exception as 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)