mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-11 21:53:37 +00:00
0d9fbc1ad7
This version of SickBeard uses both TVDB and TVRage to search and gather it's series data from allowing you to now have access to and download shows that you couldn't before because of being locked into only what TheTVDB had to offer. Also this edition is based off the code we used in our XEM editon so it does come with scene numbering support as well as all the other features our XEM edition has to offer. Please before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present, we HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk! Enjoy!
196 lines
6 KiB
Python
196 lines
6 KiB
Python
# -*- 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 .cookies import extract_cookies_to_jar
|
|
from .utils import parse_dict_header
|
|
|
|
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 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 Authentication 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 = {}
|
|
self.pos = None
|
|
|
|
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')
|
|
opaque = self.chal.get('opaque')
|
|
|
|
if algorithm is None:
|
|
_algorithm = 'MD5'
|
|
else:
|
|
_algorithm = algorithm.upper()
|
|
# lambdas assume digest modules are imported at the top level
|
|
if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
|
|
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
|
|
|
|
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)
|
|
|
|
HA1 = hash_utf8(A1)
|
|
HA2 = hash_utf8(A2)
|
|
|
|
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, HA2)
|
|
if _algorithm == 'MD5-SESS':
|
|
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
|
|
|
|
if qop is None:
|
|
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
|
|
elif qop == 'auth' or 'auth' in qop.split(','):
|
|
respdig = KD(HA1, noncebit)
|
|
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 algorithm:
|
|
base += ', algorithm="%s"' % algorithm
|
|
if entdig:
|
|
base += ', digest="%s"' % entdig
|
|
if qop:
|
|
base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
|
|
|
|
return 'Digest %s' % (base)
|
|
|
|
def handle_401(self, r, **kwargs):
|
|
"""Takes the given response and tries digest-auth, if needed."""
|
|
|
|
if self.pos is not None:
|
|
# Rewind the file position indicator of the body to where
|
|
# it was to resend the request.
|
|
r.request.body.seek(self.pos)
|
|
num_401_calls = getattr(self, 'num_401_calls', 1)
|
|
s_auth = r.headers.get('www-authenticate', '')
|
|
|
|
if 'digest' in s_auth.lower() and num_401_calls < 2:
|
|
|
|
setattr(self, 'num_401_calls', num_401_calls + 1)
|
|
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
|
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
|
|
|
# Consume content and release the original connection
|
|
# to allow our new request to reuse the same one.
|
|
r.content
|
|
r.raw.release_conn()
|
|
prep = r.request.copy()
|
|
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
|
prep.prepare_cookies(prep._cookies)
|
|
|
|
prep.headers['Authorization'] = self.build_digest_header(
|
|
prep.method, prep.url)
|
|
_r = r.connection.send(prep, **kwargs)
|
|
_r.history.append(r)
|
|
_r.request = prep
|
|
|
|
return _r
|
|
|
|
setattr(self, 'num_401_calls', 1)
|
|
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)
|
|
try:
|
|
self.pos = r.body.tell()
|
|
except AttributeError:
|
|
pass
|
|
r.register_hook('response', self.handle_401)
|
|
return r
|