summaryrefslogtreecommitdiff
path: root/requests-0.14.0/requests/auth.py
diff options
context:
space:
mode:
Diffstat (limited to 'requests-0.14.0/requests/auth.py')
-rw-r--r--requests-0.14.0/requests/auth.py382
1 files changed, 382 insertions, 0 deletions
diff --git a/requests-0.14.0/requests/auth.py b/requests-0.14.0/requests/auth.py
new file mode 100644
index 0000000..6c5264e
--- /dev/null
+++ b/requests-0.14.0/requests/auth.py
@@ -0,0 +1,382 @@
+# -*- coding: utf-8 -*-
+
+"""
+requests.auth
+~~~~~~~~~~~~~
+
+This module contains the authentication handlers for Requests.
+"""
+
+import os
+import re
+import time
+import hashlib
+import logging
+
+from base64 import b64encode
+
+from .compat import urlparse, str
+from .utils import parse_dict_header
+
+try:
+ from ._oauth import (Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER, extract_params)
+
+except (ImportError, SyntaxError):
+ SIGNATURE_HMAC = None
+ SIGNATURE_TYPE_AUTH_HEADER = None
+
+try:
+ import kerberos as k
+except ImportError as exc:
+ k = None
+
+log = logging.getLogger(__name__)
+
+CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
+CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
+
+
+def _basic_auth_str(username, password):
+ """Returns a Basic Auth string."""
+
+ return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1')
+
+
+class AuthBase(object):
+ """Base class that all auth implementations derive from"""
+
+ def __call__(self, r):
+ raise NotImplementedError('Auth hooks must be callable.')
+
+
+class OAuth1(AuthBase):
+ """Signs the request using OAuth 1 (RFC5849)"""
+ def __init__(self, client_key,
+ client_secret=None,
+ resource_owner_key=None,
+ resource_owner_secret=None,
+ callback_uri=None,
+ signature_method=SIGNATURE_HMAC,
+ signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+ rsa_key=None, verifier=None):
+
+ try:
+ signature_type = signature_type.upper()
+ except AttributeError:
+ pass
+
+ self.client = Client(client_key, client_secret, resource_owner_key,
+ resource_owner_secret, callback_uri, signature_method,
+ signature_type, rsa_key, verifier)
+
+ def __call__(self, r):
+ """Add OAuth parameters to the request.
+
+ Parameters may be included from the body if the content-type is
+ urlencoded, if no content type is set an educated guess is made.
+ """
+ # split(";") because Content-Type may be "multipart/form-data; boundary=xxxxx"
+ contenttype = r.headers.get('Content-Type', '').split(";")[0].lower()
+ # extract_params will not give params unless the body is a properly
+ # formatted string, a dictionary or a list of 2-tuples.
+ decoded_body = extract_params(r.data)
+
+ # extract_params can only check the present r.data and does not know
+ # of r.files, thus an extra check is performed. We know that
+ # if files are present the request will not have
+ # Content-type: x-www-form-urlencoded. We guess it will have
+ # a mimetype of multipart/form-data and if this is not the case
+ # we assume the correct header will be set later.
+ _oauth_signed = True
+ if r.files and contenttype == CONTENT_TYPE_MULTI_PART:
+ # Omit body data in the signing and since it will always
+ # be empty (cant add paras to body if multipart) and we wish
+ # to preserve body.
+ r.url, r.headers, _ = self.client.sign(
+ unicode(r.full_url), unicode(r.method), None, r.headers)
+ elif decoded_body != None and contenttype in (CONTENT_TYPE_FORM_URLENCODED, ''):
+ # Normal signing
+ if not contenttype:
+ r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED
+ r.url, r.headers, r.data = self.client.sign(
+ unicode(r.full_url), unicode(r.method), r.data, r.headers)
+ else:
+ _oauth_signed = False
+ if _oauth_signed:
+ # Both flows add params to the URL by using r.full_url,
+ # so this prevents adding it again later
+ r.params = {}
+
+ # Having the authorization header, key or value, in unicode will
+ # result in UnicodeDecodeErrors when the request is concatenated
+ # by httplib. This can easily be seen when attaching files.
+ # Note that simply encoding the value is not enough since Python
+ # saves the type of first key set. Thus we remove and re-add.
+ # >>> d = {u'a':u'foo'}
+ # >>> d['a'] = 'foo'
+ # >>> d
+ # { u'a' : 'foo' }
+ u_header = unicode('Authorization')
+ if u_header in r.headers:
+ auth_header = r.headers[u_header].encode('utf-8')
+ del r.headers[u_header]
+ r.headers['Authorization'] = auth_header
+
+ return r
+
+
+class HTTPBasicAuth(AuthBase):
+ """Attaches HTTP Basic Authentication to the given Request object."""
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
+
+ def __call__(self, r):
+ r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
+ return r
+
+
+class HTTPProxyAuth(HTTPBasicAuth):
+ """Attaches HTTP Proxy Authenetication to a given Request object."""
+ def __call__(self, r):
+ r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
+ return r
+
+
+class HTTPDigestAuth(AuthBase):
+ """Attaches HTTP Digest Authentication to the given Request object."""
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
+ self.last_nonce = ''
+ self.nonce_count = 0
+ self.chal = {}
+
+ def build_digest_header(self, method, url):
+
+ realm = self.chal['realm']
+ nonce = self.chal['nonce']
+ qop = self.chal.get('qop')
+ algorithm = self.chal.get('algorithm', 'MD5')
+ opaque = self.chal.get('opaque', None)
+
+ algorithm = algorithm.upper()
+ # lambdas assume digest modules are imported at the top level
+ if algorithm == 'MD5':
+ def md5_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.md5(x).hexdigest()
+ hash_utf8 = md5_utf8
+ elif algorithm == 'SHA':
+ def sha_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.sha1(x).hexdigest()
+ hash_utf8 = sha_utf8
+ # XXX MD5-sess
+ KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
+
+ if hash_utf8 is None:
+ return None
+
+ # XXX not implemented yet
+ entdig = None
+ p_parsed = urlparse(url)
+ path = p_parsed.path
+ if p_parsed.query:
+ path += '?' + p_parsed.query
+
+ A1 = '%s:%s:%s' % (self.username, realm, self.password)
+ A2 = '%s:%s' % (method, path)
+
+ if qop == 'auth':
+ if nonce == self.last_nonce:
+ self.nonce_count += 1
+ else:
+ self.nonce_count = 1
+
+ ncvalue = '%08x' % self.nonce_count
+ s = str(self.nonce_count).encode('utf-8')
+ s += nonce.encode('utf-8')
+ s += time.ctime().encode('utf-8')
+ s += os.urandom(8)
+
+ cnonce = (hashlib.sha1(s).hexdigest()[:16])
+ noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, hash_utf8(A2))
+ respdig = KD(hash_utf8(A1), noncebit)
+ elif qop is None:
+ respdig = KD(hash_utf8(A1), "%s:%s" % (nonce, hash_utf8(A2)))
+ else:
+ # XXX handle auth-int.
+ return None
+
+ self.last_nonce = nonce
+
+ # XXX should the partial digests be encoded too?
+ base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
+ 'response="%s"' % (self.username, realm, nonce, path, respdig)
+ if opaque:
+ base += ', opaque="%s"' % opaque
+ if entdig:
+ base += ', digest="%s"' % entdig
+ base += ', algorithm="%s"' % algorithm
+ if qop:
+ base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce)
+
+ return 'Digest %s' % (base)
+
+ def handle_401(self, r):
+ """Takes the given response and tries digest-auth, if needed."""
+
+ num_401_calls = r.request.hooks['response'].count(self.handle_401)
+
+ s_auth = r.headers.get('www-authenticate', '')
+
+ if 'digest' in s_auth.lower() and num_401_calls < 2:
+
+ self.chal = parse_dict_header(s_auth.replace('Digest ', ''))
+
+ # Consume content and release the original connection
+ # to allow our new request to reuse the same one.
+ r.content
+ r.raw.release_conn()
+
+ r.request.headers['Authorization'] = self.build_digest_header(r.request.method, r.request.url)
+ r.request.send(anyway=True)
+ _r = r.request.response
+ _r.history.append(r)
+
+ return _r
+
+ return r
+
+ def __call__(self, r):
+ # If we have a saved nonce, skip the 401
+ if self.last_nonce:
+ r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
+ r.register_hook('response', self.handle_401)
+ return r
+
+
+def _negotiate_value(r):
+ """Extracts the gssapi authentication token from the appropriate header"""
+
+ authreq = r.headers.get('www-authenticate', None)
+
+ if authreq:
+ rx = re.compile('(?:.*,)*\s*Negotiate\s*([^,]*),?', re.I)
+ mo = rx.search(authreq)
+ if mo:
+ return mo.group(1)
+
+ return None
+
+
+class HTTPKerberosAuth(AuthBase):
+ """Attaches HTTP GSSAPI/Kerberos Authentication to the given Request object."""
+ def __init__(self, require_mutual_auth=True):
+ if k is None:
+ raise Exception("Kerberos libraries unavailable")
+ self.context = None
+ self.require_mutual_auth = require_mutual_auth
+
+ def generate_request_header(self, r):
+ """Generates the gssapi authentication token with kerberos"""
+
+ host = urlparse(r.url).netloc
+ tail, _, head = host.rpartition(':')
+ domain = tail if tail else head
+
+ result, self.context = k.authGSSClientInit("HTTP@%s" % domain)
+
+ if result < 1:
+ raise Exception("authGSSClientInit failed")
+
+ result = k.authGSSClientStep(self.context, _negotiate_value(r))
+
+ if result < 0:
+ raise Exception("authGSSClientStep failed")
+
+ response = k.authGSSClientResponse(self.context)
+
+ return "Negotiate %s" % response
+
+ def authenticate_user(self, r):
+ """Handles user authentication with gssapi/kerberos"""
+
+ auth_header = self.generate_request_header(r)
+ log.debug("authenticate_user(): Authorization header: %s" % auth_header)
+ r.request.headers['Authorization'] = auth_header
+ r.request.send(anyway=True)
+ _r = r.request.response
+ _r.history.append(r)
+ log.debug("authenticate_user(): returning %s" % _r)
+ return _r
+
+ def handle_401(self, r):
+ """Handles 401's, attempts to use gssapi/kerberos authentication"""
+
+ log.debug("handle_401(): Handling: 401")
+ if _negotiate_value(r) is not None:
+ _r = self.authenticate_user(r)
+ log.debug("handle_401(): returning %s" % _r)
+ return _r
+ else:
+ log.debug("handle_401(): Kerberos is not supported")
+ log.debug("handle_401(): returning %s" % r)
+ return r
+
+ def handle_other(self, r):
+ """Handles all responses with the exception of 401s.
+
+ This is necessary so that we can authenticate responses if requested"""
+
+ log.debug("handle_other(): Handling: %d" % r.status_code)
+ self.deregister(r)
+ if self.require_mutual_auth:
+ if _negotiate_value(r) is not None:
+ log.debug("handle_other(): Authenticating the server")
+ _r = self.authenticate_server(r)
+ log.debug("handle_other(): returning %s" % _r)
+ return _r
+ else:
+ log.error("handle_other(): Mutual authentication failed")
+ raise Exception("Mutual authentication failed")
+ else:
+ log.debug("handle_other(): returning %s" % r)
+ return r
+
+ def authenticate_server(self, r):
+ """Uses GSSAPI to authenticate the server"""
+
+ log.debug("authenticate_server(): Authenticate header: %s" % _negotiate_value(r))
+ result = k.authGSSClientStep(self.context, _negotiate_value(r))
+ if result < 1:
+ raise Exception("authGSSClientStep failed")
+ _r = r.request.response
+ log.debug("authenticate_server(): returning %s" % _r)
+ return _r
+
+ def handle_response(self, r):
+ """Takes the given response and tries kerberos-auth, as needed."""
+
+ if r.status_code == 401:
+ _r = self.handle_401(r)
+ log.debug("handle_response returning %s" % _r)
+ return _r
+ else:
+ _r = self.handle_other(r)
+ log.debug("handle_response returning %s" % _r)
+ return _r
+
+ log.debug("handle_response returning %s" % r)
+ return r
+
+ def deregister(self, r):
+ """Deregisters the response handler"""
+ r.request.deregister_hook('response', self.handle_response)
+
+ def __call__(self, r):
+ r.register_hook('response', self.handle_response)
+ return r