def _parse_challenge(self, request: Request, response: Response, auth_header: str) -> "_DigestAuthChallenge": """ Returns a challenge from a Digest WWW-Authenticate header. These take the form of: `Digest realm="*****@*****.**",qop="auth,auth-int",nonce="abc",opaque="xyz"` """ scheme, _, fields = auth_header.partition(" ") # This method should only ever have been called with a Digest auth header. assert scheme.lower() == "digest" header_dict: typing.Dict[str, str] = {} for field in parse_http_list(fields): key, value = field.strip().split("=", 1) header_dict[key] = unquote(value) try: realm = header_dict["realm"].encode() nonce = header_dict["nonce"].encode() qop = header_dict["qop"].encode() if "qop" in header_dict else None opaque = header_dict["opaque"].encode( ) if "opaque" in header_dict else None algorithm = header_dict.get("algorithm", "MD5") return _DigestAuthChallenge(realm=realm, nonce=nonce, qop=qop, opaque=opaque, algorithm=algorithm) except KeyError as exc: message = "Malformed Digest WWW-Authenticate header" raise ProtocolError(message, request=request) from exc
def _parse_challenge(self, header: str) -> "_DigestAuthChallenge": """ Returns a challenge from a Digest WWW-Authenticate header. These take the form of: `Digest realm="*****@*****.**",qop="auth,auth-int",nonce="abc",opaque="xyz"` """ scheme, _, fields = header.partition(" ") if scheme.lower() != "digest": raise ProtocolError("Header does not start with 'Digest'") header_dict: typing.Dict[str, str] = {} for field in parse_http_list(fields): key, value = field.strip().split("=", 1) header_dict[key] = unquote(value) try: realm = header_dict["realm"].encode() nonce = header_dict["nonce"].encode() qop = header_dict["qop"].encode() if "qop" in header_dict else None opaque = header_dict["opaque"].encode( ) if "opaque" in header_dict else None algorithm = header_dict.get("algorithm", "MD5") return _DigestAuthChallenge(realm=realm, nonce=nonce, qop=qop, opaque=opaque, algorithm=algorithm) except KeyError as exc: raise ProtocolError( "Malformed Digest WWW-Authenticate header") from exc
def process_request(self, url, body, headers): if not headers: headers = {} self.logger.debug("[%s] Process middleware on: %s", self.name, url) # First query - 401 code, resp_headers, result = fetch_sync( url, headers=None, request_timeout=60, follow_redirects=True, allow_proxy=False, validate_cert=False, eof_mark=self.eof_mark, ) self.logger.debug( "[%s] Response code %s, headers %s on: %s, body: %s", self.name, code, resp_headers, url, body, ) if "WWW-Authenticate" in resp_headers and resp_headers["WWW-Authenticate"].startswith( "Digest" ): items = parse_http_list(resp_headers["WWW-Authenticate"][7:]) digest_response = parse_keqv_list(items) headers["Authorization"] = self.build_digest_header(url, self.method, digest_response) self.logger.debug("[%s] Set headers, %s", self.name, headers) return url, body, headers
def _fetch_oauth2_token(self, host, response_headers): www_auth = response_headers['WWW-Authenticate'] dockercfg = self.pull_secret.for_host(host) if not www_auth.startswith('Bearer ') or not dockercfg: return None items = parse_http_list(www_auth[7:]) opts = parse_keqv_list(items) # realm, scope, service realm = urlparse(opts['realm']) url = ('{path}' '?client_id=docker' '&offline_token=true' '&service={service}' '&scope={scope}').format(path=realm.path, **opts) c = http.client.HTTPSConnection(realm.netloc) c.request('GET', url, headers={ 'Authorization': "Basic {}".format(dockercfg['auth']) }) r = c.getresponse() if r.status != 200: return False data = json.loads(r.read()) self.headers['Authorization'] = 'Bearer {}'.format(data['token']) return True
async def learn_cache_key(request: Request, response: Response, *, cache: Cache) -> str: """ Generate a cache key from the requested absolute URL. Varying response headers are stored at another key based from the requested absolute URL. """ logger.trace("learn_cache_key " f"request.method={request.method!r} " f"response.headers.Vary={response.headers.get('Vary')!r}") varying_headers_cache_key = generate_varying_headers_cache_key(request, cache=cache) varying_headers: typing.List[str] = [] if "Vary" in response.headers: for header in parse_http_list(response.headers["Vary"]): varying_headers.append(header.lower()) varying_headers.sort() logger.trace( "store_varying_headers " f"cache_key={varying_headers_cache_key!r} headers={varying_headers!r}") await cache.set(key=varying_headers_cache_key, value=varying_headers) return generate_cache_key(request, method=request.method, varying_headers=varying_headers, cache=cache)
def parse_www_authenticate_header(header): """ Convert a WWW-Authentication header into a dict that can be used in a JSON response. """ items = parse_http_list(header) return parse_keqv_list(items)
def authorization(self) -> Optional[Authorization]: header = self.headers.get("Authorization", "") try: type_, value = header.split(None, 1) type_ = type_.lower() except ValueError: return None else: if type_ == "basic": try: username, password = b64decode( value.encode()).decode().split(":", 1) except ValueError: return None else: return Authorization(username=username, password=password) elif type_ == "digest": items = parse_http_list(value) params = parse_keqv_list(items) for key in "username", "realm", "nonce", "uri", "response": if key not in params: return None if ("cnonce" in params or "nc" in params) and "qop" not in params: return None return Authorization(**params) return None
def authorization(self) -> Optional[Authorization]: header = self.headers.get('Authorization', '') try: type_, value = header.split(None, 1) type_ = type_.lower() except ValueError: return None else: if type_ == 'basic': try: username, password = b64decode( value.encode()).decode().split(':', 1) except ValueError: return None else: return Authorization(username=username, password=password) elif type_ == 'digest': items = parse_http_list(value) params = parse_keqv_list(items) for key in 'username', 'realm', 'nonce', 'uri', 'response': if key not in params: return None if ('cnonce' in params or 'nc' in params) and 'qop' not in params: return None return Authorization(**params) return None
def login(wwwAuth, repository, host=None, forceCredential=False): authresp = wwwAuth realm = parse_keqv_list(parse_http_list(authresp[authresp.index(' ') + 1:])) realmurl = urlparse(realm['realm']) credentialHost = host if host is not None else realmurl.netloc with closing(httplib.HTTPSConnection(realmurl.netloc)) as authhttps: authhttps.request( 'GET', '%s?scope=repository:%s:pull&service=%s' % (realmurl.path, repository, realm['service']), None) resp = authhttps.getresponse() if forceCredential or resp.status == 401 or resp.status == 403: resp.read() basic = getCredential(credentialHost) if basic: account = base64.b64decode(basic).decode('utf-8').split(':')[0] authhttps.request( 'GET', '%s?account=%s&scope=repository:%s:pull&service=%s' % (realmurl.path, account, repository, realm['service']), None, {'Authorization': 'Basic ' + basic}) resp = authhttps.getresponse() if resp.status == 401: raise Exception( 'Credential is wrong (used "%s"). Please relogin to %s.' % (account, credentialHost)) else: raise Exception('`docker login %s` is required.' % (credentialHost)) token = json.load(resp)['token'] return {'Authorization': 'Bearer ' + token}
def __init__( self, auth_header, http_method, debug=False, accept_charset=DEFAULT_CHARSET[:], ): self.http_method = http_method self.debug = debug if not self.matches(auth_header): raise ValueError('Authorization scheme is not "Digest"') self.auth_header = _try_decode_header(auth_header, accept_charset) scheme, params = self.auth_header.split(' ', 1) # make a dict of the params items = parse_http_list(params) paramsd = parse_keqv_list(items) self.realm = paramsd.get('realm') self.username = paramsd.get('username') self.nonce = paramsd.get('nonce') self.uri = paramsd.get('uri') self.method = paramsd.get('method') self.response = paramsd.get('response') # the response digest self.algorithm = paramsd.get('algorithm', 'MD5').upper() self.cnonce = paramsd.get('cnonce') self.opaque = paramsd.get('opaque') self.qop = paramsd.get('qop') # qop self.nc = paramsd.get('nc') # nonce count # perform some correctness checks if self.algorithm not in valid_algorithms: raise ValueError( self.errmsg("Unsupported value for algorithm: '%s'" % self.algorithm)) has_reqd = ( self.username and self.realm and self.nonce and self.uri and self.response ) if not has_reqd: raise ValueError( self.errmsg('Not all required parameters are present.')) if self.qop: if self.qop not in valid_qops: raise ValueError( self.errmsg("Unsupported value for qop: '%s'" % self.qop)) if not (self.cnonce and self.nc): raise ValueError( self.errmsg('If qop is sent then ' 'cnonce and nc MUST be present')) else: if self.cnonce or self.nc: raise ValueError( self.errmsg('If qop is not sent, ' 'neither cnonce nor nc can be present'))
def _get_auth_info(self, response): """Parse a 401 response and get the needed auth parameters.""" www_auth = response.headers["Www-Authenticate"] if not www_auth.startswith("Bearer "): raise ValueError("Bearer not found") info = parse_keqv_list(parse_http_list(www_auth[7:])) return info
def parse_authorization_header(header): if isinstance(header, bytes): header = header.decode('ascii') # HTTP headers cannot be Unicode. auth = header.split(' ', 1) if len(auth) > 2: raise VerifierException('Invalid authorization header. (eg. Method ' + 'key1=value1,key2="value, \"2\"")') # Split up any args into a dictionary. values = multidict.CIMultiDict() if len(auth) == 2: auth_value = auth[1] if auth_value: # This is tricky string magic. Let urllib do it. fields = parse_http_list(auth_value) for item in fields: # Only include keypairs. if '=' in item: # Split on the first '=' only. key, value = item.split('=', 1) if not key or not value: continue # Unquote values, if quoted. if value[0] == '"': value = value[1:-1] values[key] = value # ("Signature", {"headers": "date", "algorithm": "hmac-sha256", ... }) return (auth[0], values)
def parse_authorization_header(header): if not isinstance(header, six.string_types): header = header.decode("ascii") #HTTP headers cannot be Unicode. auth = header.split(" ", 1) if len(auth) > 2: raise ValueError('Invalid authorization header. (eg. Method key1=value1,key2="value, \"2\"")') # Split up any args into a dictionary. values = {} if len(auth) == 2: auth_value = auth[1] if auth_value and len(auth_value): # This is tricky string magic. Let urllib do it. fields = parse_http_list(auth_value) for item in fields: # Only include keypairs. if '=' in item: # Split on the first '=' only. key, value = item.split('=', 1) if not (len(key) and len(value)): continue # Unquote values, if quoted. if value[0] == '"': value = value[1:-1] values[key] = value # ("Signature", {"headers": "date", "algorithm": "hmac-sha256", ... }) return (auth[0], CaseInsensitiveDict(values))
def from_header( cls: Type['HeaderSet'], header: str, on_update: Optional[Callable] = None, ) -> 'HeaderSet': items = {item for item in parse_http_list(header)} return cls(items, on_update=on_update)
def get_ip_from_request(request: object) -> str: """Extracts IP address from a request object""" # Code inspired by Flask's werkzeug/wrappers/base_request.py if 'HTTP_X_FORWARDED_FOR' in request.META: addr = parse_http_list(request.META.get('HTTP_X_FORWARDED_FOR')) if len(addr) > 0: return addr[0].strip('"') # else return request.META.get('REMOTE_ADDR')
def wrapped(self): if not hasattr(self, 'authenticated'): self.authenticated = None auth = self.headers.get('Authorization') if not self.authenticated and auth is not None: token, fields = auth.split(' ', 1) if token == 'Digest': cred = parse_http_list(fields) cred = parse_keqv_list(cred) # The request must contain all these keys to # constitute a valid response. keys = 'realm username nonce uri response'.split() if not all(cred.get(key) for key in keys): self.authenticated = False elif cred['realm'] != realm or cred['username'] not in accounts: self.authenticated = False elif 'qop' in cred and ('nc' not in cred or 'cnonce' not in cred): self.authenticated = False else: location = '%s:%s' % (self.command, self.path) location = location.encode('utf8') location = md5hex(location) password = accounts.get_password(cred['username']) if 'qop' in cred: info = (cred['nonce'], cred['nc'], cred['cnonce'], cred['qop'], location) else: info = cred['nonce'], location expect = '%s:%s' % (password, ':'.join(info)) expect = md5hex(expect.encode('utf8')) self.authenticated = (expect == cred['response']) if self.authenticated: self.crunchy_username = cred['username'] if self.authenticated is None: msg = "You are not allowed to access this page. Please login first!" elif self.authenticated is False: msg = "Authenticated Failed" if not self.authenticated : self.send_response(401) nonce = ("%d:%s" % (time.time(), realm)).encode('utf8') self.send_header('WWW-Authenticate', 'Digest realm="%s",' 'qop="auth",' 'algorithm="MD5",' 'nonce="%s"' % (realm, nonce)) self.end_headers() self.wfile.write(msg.encode('utf8')) else: return func(self)
def _parse_digest_header(header): fields = {} fields_ = parse_http_list(header) for field in fields_: k, v = field.split('=', 1) v = v.strip() if v and v[0] == v[-1] == '"': v = v[1:-1] fields[k.strip().lower()] = v return fields
def wrapped(self): if not hasattr(self, 'authenticated'): self.authenticated = None if self.authenticated: return func(self) auth = self.headers.get('Authorization') if auth is None: msg = "You are not allowed to access this page. Please login first!" return _error_401(self, msg) token, fields = auth.split(' ', 1) if token != 'Digest': return _error_401(self, 'Unsupported authentication type') # Check the header fields of the request. cred = parse_http_list(fields) cred = parse_keqv_list(cred) keys = 'realm', 'username', 'nonce', 'uri', 'response' if not all(cred.get(key) for key in keys): return _error_401(self, 'Incomplete authentication header') if cred['realm'] != self.server.realm: return _error_401(self, 'Incorrect realm') if 'qop' in cred and ('nc' not in cred or 'cnonce' not in cred): return _error_401(self, 'qop with missing nc or cnonce') # Check the username. username = cred['username'] password = self.server.get_password(username) if not username or password is None: return _error_401(self, 'Invalid username or password') # Check the digest string. location = '%s:%s' % (self.command, self.path) location = md5hex(location.encode('utf8')) pwhash = md5hex('%s:%s:%s' % (username, self.server.realm, password)) if 'qop' in cred: info = (cred['nonce'], cred['nc'], cred['cnonce'], cred['qop'], location) else: info = cred['nonce'], location expect = '%s:%s' % (pwhash, ':'.join(info)) expect = md5hex(expect.encode('utf8')) if expect != cred['response']: return _error_401(self, 'Invalid username or password') # Success! self.authenticated = True return func(self)
def _parse_digest_header(header): from urllib.request import parse_http_list fields = {} for field in parse_http_list(header): k, v = field.split('=', 1) v = v.strip().strip('"') fields[k.lower()] = v return fields
def __init__(self, header_value: str) -> None: self.options: List[AcceptOption] = [] for accept_option in parse_http_list(header_value): try: option, raw_quality = accept_option.split(';') except ValueError: option = accept_option quality = 1.0 else: quality = float(raw_quality.split('=', 1)[-1]) self.options.append(AcceptOption(option, quality))
def parse_dict_header(value): """Parse key=value pairs from value list """ result = {} for item in parse_http_list(value): if "=" not in item: result[item] = None continue name, value = item.split('=', 1) if value[:1] == value[-1:] == '"': value = urllib.parse.unquote(value[1:-1]) # strip " and unquote result[name] = value return result
def authorized(self): tcs = self.server.test_case_server auth_header = self.headers.get(tcs.auth_header_recv, None) if auth_header is None: return False scheme, auth = auth_header.split(None, 1) if scheme.lower() == tcs.auth_scheme: auth_dict = parse_keqv_list(parse_http_list(auth)) return tcs.digest_authorized(auth_dict, self.command) return False
def from_header(cls: Type['ETags'], header: str) -> 'ETags': header = header.strip() weak = set() strong = set() if header == '*': return ETags(star=True) else: for item in parse_http_list(header): if item.upper().startswith('W/'): weak.add(item[2:].strip('"')) else: strong.add(item.strip('"')) return ETags(weak, strong)
def from_header( cls: Type['_CacheControl'], header: str, on_update: Optional[Callable] = None, ) -> '_CacheControl': cache_control = cls(on_update) for item in parse_http_list(header): if '=' in item: for key, value in parse_keqv_list([item]).items(): cache_control._directives[key] = value else: cache_control._directives[item] = True return cache_control
def wrapped(self): if not hasattr(self, 'authenticated'): self.authenticated = None if self.authenticated: return func(self) auth = self.headers.get(u'Authorization') if auth is None: msg = u"You are not allowed to access this page. Please login first!" return _error_401(self, msg) token, fields = auth.split(' ', 1) if token != 'Digest': return _error_401(self, 'Unsupported authentication type') # Check the header fields of the request. cred = parse_http_list(fields) cred = parse_keqv_list(cred) keys = u'realm', u'username', u'nonce', u'uri', u'response' if not all(cred.get(key) for key in keys): return _error_401(self, 'Incomplete authentication header') if cred['realm'] != self.server.realm: return _error_401(self, 'Incorrect realm') if 'qop' in cred and ('nc' not in cred or 'cnonce' not in cred): return _error_401(self, 'qop with missing nc or cnonce') # Check the username. username = cred['username'] password = self.server.get_password(username) if not username or password is None: return _error_401(self, 'Invalid username or password') # Check the digest string. location = u'%s:%s' % (self.command, self.path) location = md5hex(location.encode('utf8')) pwhash = md5hex('%s:%s:%s' % (username, self.server.realm, password)) if 'qop' in cred: info = (cred['nonce'], cred['nc'], cred['cnonce'], cred['qop'], location) else: info = cred['nonce'], location expect = u'%s:%s' % (pwhash, ':'.join(info)) expect = md5hex(expect.encode('utf8')) if expect != cred['response']: return _error_401(self, 'Invalid username or password') # Success! self.authenticated = True return func(self)
def compose(self, digest=None, basic=None, username=None, password=None, challenge=None, path=None, method=None): assert username and password if basic or not challenge: assert not digest userpass = "******" % (username.strip(), password.strip()) return "Basic %s" % userpass.encode('base64').strip() assert challenge and not basic path = path or "/" (_, realm) = challenge.split('realm="') (realm, _) = realm.split('"', 1) auth = AbstractDigestAuthHandler() auth.add_password(realm, path, username, password) (token, challenge) = challenge.split(' ', 1) chal = parse_keqv_list(parse_http_list(challenge)) class FakeRequest(object): if six.PY3: @property def full_url(self): return path selector = full_url @property def data(self): return None else: def get_full_url(self): return path get_selector = get_full_url def has_data(self): return False def get_method(self): return method or "GET" retval = "Digest %s" % auth.get_authorization(FakeRequest(), chal) return (retval, )
def parse_www_authenticate_header(header: str) -> Tuple[str, Dict[str, str]]: ''' Parse WWW-Authenticate header used in OAuth2 authentication for container registries. This is NOT RFC-compliant! Simplified from http.parse_www_authenticate_header in Werkzeug (BSD license) ''' auth_type, auth_info = header.split(None, 1) result = {} for item in parse_http_list(auth_info): name, value = item.split("=", 1) if value[:1] == value[-1:] == '"': value = value[1:-1] result[name] = value return auth_type, result
def get_opener(self): # The opener is yet build ? if self.opener is not None: return self.opener # Build a new opener opener = build_opener() headers = [ ('User-agent', 'restedit/%s' % __version__) ] # Add the "includes" for include in self.includes: headers.append(include) # An authentication ? auth_header = self.metadata.get('auth') if auth_header is not None: if auth_header.lower().startswith('basic'): cls_handler = HTTPBasicAuthHandler chal = auth_header[6:].strip() # Automatically find the username and the password username, password = decode_base64(chal).split(':', 1) elif auth_header.lower().startswith('digest'): cls_handler = HTTPDigestAuthHandler # Automatically find the username, but we must ask the password # XXX undocumented functions chal = parse_keqv_list(parse_http_list(auth_header[7:])) username = chal['username'] password = askPassword(chal['realm'], username) else: raise NotImplemented password_mgr = HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(realm=None, uri=self.url, user=username, passwd=password) auth_handler = cls_handler(password_mgr) opener.add_handler(auth_handler) # A cookie ? if self.metadata.get('cookie'): headers.append( ('Cookie', self.metadata['cookie']) ) # All OK opener.addheaders = headers self.opener = opener return opener
def from_header(cls: Type['Range'], header: str) -> 'Range': try: units, raw_ranges = header.split('=', 1) except ValueError: return cls('', []) units = units.strip().lower() ranges = [] for range_set in parse_http_list(raw_ranges): if range_set.startswith('-'): ranges.append(RangeSet(int(range_set), None)) elif '-' in range_set: begin, end = range_set.split('-') ranges.append(RangeSet(int(begin), int(end))) else: ranges.append(RangeSet(0, int(range_set))) return Range(units, ranges)
def _parse_authorization_header(headers): """Parse an OAuth authorization header into a list of 2-tuples""" authorization_header = headers.get('Authorization') if not authorization_header: return [], None auth_scheme = 'oauth ' if authorization_header.lower().startswith(auth_scheme): items = parse_http_list(authorization_header[len(auth_scheme):]) try: items = parse_keqv_list(items).items() auth_params = [(unescape(k), unescape(v)) for k, v in items] realm = dict(auth_params).get('realm') return auth_params, realm except (IndexError, ValueError): pass raise ValueError('Malformed authorization header')
def parse_rtsp_header(data: bytes) -> Tuple[int, Dict[str, Any], bytes]: code, headers, msg = 200, {}, b"" for line in data.splitlines(): if line.startswith(b"RTSP/1.0"): _, code, msg = line.split(None, 2) elif b":" in line: h, v = line.split(b":", 1) h, v = smart_text(h), smart_text(v) if h in MULTIPLE_HEADER: if h not in headers: headers[h] = {} auth, line = v.split(None, 1) items = parse_http_list(line) headers[h][auth] = parse_keqv_list(items) continue headers[h] = v.strip() return int(code), headers, msg
def patch_cache_control(headers: MutableHeaders, **kwargs: typing.Any) -> None: """ Patch headers with an extended version of the initial Cache-Control header by adding all keyword arguments to it. """ cache_control: typing.Dict[str, typing.Any] = {} for field in parse_http_list(headers.get("Cache-Control", "")): try: key, value = field.split("=") except ValueError: cache_control[field] = True else: cache_control[key] = value if "max-age" in cache_control and "max_age" in kwargs: kwargs["max_age"] = min(int(cache_control["max-age"]), kwargs["max_age"]) if "public" in kwargs: raise NotImplementedError( "The 'public' cache control directive isn't supported yet.") if "private" in kwargs: raise NotImplementedError( "The 'private' cache control directive isn't supported yet.") for key, value in kwargs.items(): key = key.replace("_", "-") cache_control[key] = value directives: typing.List[str] = [] for key, value in cache_control.items(): if value is False: continue if value is True: directives.append(key) else: directives.append(f"{key}={value}") patched_cache_control = ", ".join(directives) if patched_cache_control: headers["Cache-Control"] = patched_cache_control else: del headers["Cache-Control"]
def from_header(cls, header: str) -> "DigestAuthChallenge": """Returns a challenge from a Digest WWW-Authenticate header. These take the form of: `Digest realm="*****@*****.**",qop="auth,auth-int",nonce="abc",opaque="xyz"` """ scheme, _, fields = header.partition(" ") if scheme.lower() != "digest": raise ValueError("Header does not start with 'Digest'") header_dict: typing.Dict[str, str] = {} for field in parse_http_list(fields): key, value = field.strip().split("=", 1) header_dict[key] = unquote(value) try: return cls.from_header_dict(header_dict) except KeyError as exc: raise ValueError("Malformed Digest WWW-Authenticate header") from exc
def parse_signature_header(sign_value): values = {} if sign_value: # This is tricky string magic. Let urllib do it. fields = parse_http_list(sign_value) for item in fields: # Only include keypairs. if '=' in item: # Split on the first '=' only. key, value = item.split('=', 1) if not (len(key) and len(value)): continue # Unquote values, if quoted. if value[0] == '"': value = value[1:-1] values[key] = value return CaseInsensitiveDict(values)
def compose(self, digest=None, basic=None, username=None, password=None, challenge=None, path=None, method=None): assert username and password if basic or not challenge: assert not digest userpass = "******" % (username.strip(), password.strip()) return "Basic %s" % userpass.encode('base64').strip() assert challenge and not basic path = path or "/" (_, realm) = challenge.split('realm="') (realm, _) = realm.split('"', 1) auth = AbstractDigestAuthHandler() auth.add_password(realm, path, username, password) (token, challenge) = challenge.split(' ', 1) chal = parse_keqv_list(parse_http_list(challenge)) class FakeRequest(object): if six.PY3: @property def full_url(self): return path selector = full_url @property def data(self): return None else: def get_full_url(self): return path get_selector = get_full_url def has_data(self): return False def get_method(self): return method or "GET" retval = "Digest %s" % auth.get_authorization(FakeRequest(), chal) return (retval,)
def _parseDigestAuthorization(auth_params): # Convert the auth params to a dict items = parse_http_list(auth_params) params = parse_keqv_list(items) # Now validate the params # Check for required parameters required = ["username", "realm", "nonce", "uri", "response"] for k in required: if k not in params: return None # If qop is sent then cnonce and nc MUST be present if "qop" in params and not ("cnonce" in params and "nc" in params): return None # If qop is not sent, neither cnonce nor nc can be present if ("cnonce" in params or "nc" in params) and "qop" not in params: return None return params
def parse_list_header(value): """Parse lists as described by RFC 2068 Section 2. In particular, parse comma-separated lists where the elements of the list may include quoted-strings. A quoted-string could contain a comma. A non-quoted string could have quotes in the middle. Quotes are removed automatically after parsing. The return value is a standard :class:`list`: >>> parse_list_header('token, "quoted value"') ['token', 'quoted value'] :param value: a string with a list header. :return: :class:`list` """ result = [] for item in urllib2.parse_http_list(value): if item[:1] == item[-1:] == '"': item = unquote_header_value(item[1:-1]) result.append(item) return result
def parse_authorization_header(value): """Parse the Authenticate header. Returns nothing on failure, opts hash on success with type='basic' or 'digest' and other params. <http://nullege.com/codes/search/werkzeug.http.parse_authorization_header> <http://stackoverflow.com/questions/1349367/parse-an-http-request-authorization-header-with-python> <http://bugs.python.org/file34041/0001-Add-an-authorization-header-to-the-initial-request.patch> """ try: (auth_type, auth_info) = value.split(' ', 1) auth_type = auth_type.lower() except ValueError: return if (auth_type == 'basic'): try: decoded = base64.b64decode(auth_info).decode( 'utf-8') # b64decode gives bytes in python3 (username, password) = decoded.split(':', 1) except (ValueError, TypeError): # py3, py2 return return {'type': 'basic', 'username': username, 'password': password} elif (auth_type == 'digest'): try: auth_map = parse_keqv_list(parse_http_list(auth_info)) except ValueError: return logging.debug(auth_map) for key in 'username', 'realm', 'nonce', 'uri', 'response': if key not in auth_map: return if 'qop' in auth_map and ('nc' not in auth_map or 'cnonce' not in auth_map): return auth_map['type'] = 'digest' return auth_map else: # unknown auth type return
def parse_authorization_header(value): """Parse the Authenticate header. Returns nothing on failure, opts hash on success with type='basic' or 'digest' and other params. <http://nullege.com/codes/search/werkzeug.http.parse_authorization_header> <http://stackoverflow.com/questions/1349367/parse-an-http-request-authorization-header-with-python> <http://bugs.python.org/file34041/0001-Add-an-authorization-header-to-the-initial-request.patch> """ try: (auth_type, auth_info) = value.split(" ", 1) auth_type = auth_type.lower() except ValueError as e: return if auth_type == "basic": try: decoded = base64.b64decode(auth_info).decode("utf-8") # b64decode gives bytes in python3 (username, password) = decoded.split(":", 1) except ValueError: # Exception as e: return return {"type": "basic", "username": username, "password": password} elif auth_type == "digest": auth_map = parse_keqv_list(parse_http_list(auth_info)) print(auth_map) for key in "username", "realm", "nonce", "uri", "response": if key not in auth_map: return if "qop" in auth_map: if not auth_map.get("nc") or not auth_map.get("cnonce"): return auth_map["type"] = "digest" return auth_map else: # unknown auth type return
def build_digest_response(self, fields, username, password): """ Takes a Proxy-Authenticate: Digest header and creates a response header :param fields: The string portion of the Proxy-Authenticate header after "Digest " :param username: The username to use for the response :param password: The password to use for the response :return: None if invalid Proxy-Authenticate header, otherwise the string of fields for the Proxy-Authorization: Digest header """ fields = parse_keqv_list(parse_http_list(fields)) realm = fields.get('realm') nonce = fields.get('nonce') qop = fields.get('qop') algorithm = fields.get('algorithm') if algorithm: algorithm = algorithm.lower() opaque = fields.get('opaque') if algorithm in ['md5', None]: def md5hash(string): return hashlib.md5(string).hexdigest() hash = md5hash elif algorithm == 'sha': def sha1hash(string): return hashlib.sha1(string).hexdigest() hash = sha1hash else: return None host_port = u"%s:%s" % (self.host, self.port) a1 = "%s:%s:%s" % (username, realm, password) a2 = "CONNECT:%s" % host_port ha1 = hash(a1) ha2 = hash(a2) if qop == None: response = hash(u"%s:%s:%s" % (ha1, nonce, ha2)) elif qop == 'auth': nc = '00000001' cnonce = hash(os.urandom(8))[:8] response = hash(u"%s:%s:%s:%s:%s:%s" % (ha1, nonce, nc, cnonce, qop, ha2)) else: return None response_fields = { 'username': username, 'realm': realm, 'nonce': nonce, 'response': response, 'uri': host_port } if algorithm: response_fields['algorithm'] = algorithm if qop == 'auth': response_fields['nc'] = nc response_fields['cnonce'] = cnonce response_fields['qop'] = qop if opaque: response_fields['opaque'] = opaque return ', '.join([u"%s=\"%s\"" % (field, response_fields[field]) for field in response_fields])
def parse_dict_header(value): return parse_keqv_list(parse_http_list(value))
def parse_dict_header(value): """ Parses a HTTP dict header value -- i.e. ``"foo=bar, spam=eggs"`` is parsed into ``{'foo': 'bar', 'spam': 'eggs'}``. """ return parse_keqv_list(parse_http_list(value))
def get_components_from_bearer(header): _, _, value = header.partition("Bearer") opts = parse_keqv_list(parse_http_list(value)) if value else None return opts.get('realm', None), opts.get('service', None), opts.get('scope', None)