def _get_token(self, auth_info, repo): bearer_info = parse_dict_header(self.BEARER_PATTERN.sub('', auth_info, count=1)) # If repo could not be determined, do not set scope - implies global access if repo: bearer_info['scope'] = 'repository:{}:{}'.format(repo, ','.join(self.access)) realm = bearer_info.pop('realm') realm_auth = None if self.auth_b64: realm_auth = HTTPBasicAuthWithB64(self.auth_b64) elif self.username and self.password: realm_auth = HTTPBasicAuth(self.username, self.password) realm_response = requests.get(realm, params=bearer_info, verify=self.verify, auth=realm_auth) realm_response.raise_for_status() return realm_response.json()['token']
def handle_407(self, r, **kwargs): """Handle HTTP 407 only once, otherwise give up :param r: current response :returns: responses, along with the new response """ if r.status_code == 407 and self.stale_rejects < 2: s_auth = r.headers.get("proxy-authenticate") if s_auth is None: raise IOError( "proxy server violated RFC 7235:" "407 response MUST contain header proxy-authenticate") elif not self._pat.match(s_auth): return r self.chal = utils.parse_dict_header( self._pat.sub('', s_auth, count=1)) # if we present the user/passwd and still get rejected # http://tools.ietf.org/html/rfc2617#section-3.2.1 if ('Proxy-Authorization' in r.request.headers and 'stale' in self.chal): if self.chal['stale'].lower() == 'true': # try again self.stale_rejects += 1 # wrong user/passwd elif self.chal['stale'].lower() == 'false': raise IOError("User or password is invalid") # Consume content and release the original connection # to allow our new request to reuse the same one. r.content r.close() prep = r.request.copy() cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw) prep.prepare_cookies(prep._cookies) prep.headers['Proxy-Authorization'] = self.build_digest_header( prep.method, prep.url) _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r else: # give up authenticate return r
def request(self, url: str, *args, **kwargs) -> dict: # noqa: arguments-differ """ Providing PIK API standardized auth error response for for API views by `WWW-Authenticate` header parsing. Example: If Provider returns for /api/: HTTPError(headers={'WWW-Authenticate': error="oh_error", error_description="Oh Error!"}) Transforming into: AuthenticationFailed({"code": "oh_error", message: "Oh Error!"}) """ try: return super().request(url, *args, **kwargs) except HTTPError as exc: if exc.response.status_code != 401: raise if 'WWW-Authenticate' not in exc.response.headers: raise try: view = resolve(self.strategy.request.path) except Resolver404: raise exc view = getattr(view.func, 'cls') if not issubclass(view, APIView): raise auth = parse_dict_header(exc.response.headers['WWW-Authenticate']) if not ('error' in auth and 'error_description' in auth): raise raise AuthenticationFailed(auth['error_description'].strip('"\''), auth['error'].strip('"\''))
def handle_401(self, r, **kwargs): """ Takes the given response and tries digest-auth, if needed. :rtype: requests.Response """ # If response is not 4xx, do not auth # See https://github.com/requests/requests/issues/3772 if not 400 <= r.status_code < 500: self._thread_local.num_401_calls = 1 return r if self._thread_local.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._thread_local.pos) s_auth = r.headers.get('www-authenticate', '') if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: self._thread_local.num_401_calls += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) self._thread_local.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.close() 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 self._thread_local.num_401_calls = 1 return r
def _get_token(self, auth_info, repo): bearer_info = parse_dict_header( self.BEARER_PATTERN.sub('', auth_info, count=1)) # If repo could not be determined, do not set scope - implies global access if repo: bearer_info['scope'] = 'repository:{}:{}'.format( repo, ','.join(self.access)) realm = bearer_info.pop('realm') realm_auth = None if self.username and self.password: realm_auth = HTTPBasicAuth(self.username, self.password) realm_response = requests.get(realm, params=bearer_info, verify=self.verify, auth=realm_auth) realm_response.raise_for_status() return realm_response.json()['token']
def test_good(self): r = Request() root_key = hashlib.sha256("root").hexdigest() root_macaroon = Macaroon(key=root_key) discharge_key = hashlib.sha256("discharge").hexdigest() discharge_caveat_id = '{"secret": "thing"}' root_macaroon.add_third_party_caveat( "sso.example", discharge_key, discharge_caveat_id) unbound_discharge_macaroon = Macaroon( location="sso.example", key=discharge_key, identifier=discharge_caveat_id) MacaroonAuth( root_macaroon.serialize(), unbound_discharge_macaroon.serialize())(r) auth_value = r.headers["Authorization"] self.assertThat(auth_value, StartsWith("Macaroon ")) self.assertThat( parse_dict_header(auth_value[len("Macaroon "):]), MacaroonsVerify(root_key))
def _authenticate(self, response, **kwargs): if not 400 <= response.status_code < 500: return response # we have to handle the authentication, may be token the token expired or it wasn't there at all auth_info = response.headers.get('WWW-Authenticate') if not auth_info: raise exceptions.TrinoAuthError( "Error: header WWW-Authenticate not available in the response." ) if not _OAuth2TokenBearer._BEARER_PREFIX.search(auth_info): raise exceptions.TrinoAuthError( f"Error: header info didn't match {auth_info}") auth_info_headers = parse_dict_header( _OAuth2TokenBearer._BEARER_PREFIX.sub("", auth_info, count=1)) auth_server = auth_info_headers.get('x_redirect_server') if auth_server is None: raise exceptions.TrinoAuthError( "Error: header info didn't have x_redirect_server") token_server = auth_info_headers.get('x_token_server') if token_server is None: raise exceptions.TrinoAuthError( "Error: header info didn't have x_token_server") self._thread_local.token_server = token_server # tell app that use this url to proceed with the authentication self._redirect_auth_url(auth_server) # Consume content and release the original connection # to allow our new request to reuse the same one. response.content response.close() self._thread_local.token = self._get_token(token_server, response, **kwargs) return self._retry_request(response, **kwargs)
def handle_407(self, r, **kwargs): """Handle HTTP 407 only once, otherwise give up :param r: current response :returns: responses, along with the new response """ if r.status_code == 407 and self.stale_rejects < 2: if "proxy-authenticate" not in r.headers: raise IOError( "proxy server violated RFC 7235:" "407 response MUST contain header proxy-authenticate") self.chal = utils.parse_dict_header( self._pat.sub('', r.headers['proxy-authenticate'], count=1)) # if we present the user/passwd and still get rejected # http://tools.ietf.org/html/rfc2617#section-3.2.1 if ('Proxy-Authorization' in r.request.headers and 'stale' in self.chal): if self.chal['stale'].lower() == 'true': # try again self.stale_rejects += 1 # wrong user/passwd elif self.chal['stale'].lower() == 'false': raise IOError("User or password is invalid") # Consume content and release the original connection # to allow our new request to reuse the same one. r.content r.close() prep = r.request.copy() cookies.extract_cookies_to_jar(prep._cookies, r.request, r.raw) prep.prepare_cookies(prep._cookies) prep.headers['Proxy-Authorization'] = self.build_digest_header( prep.method, prep.url) _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r else: # give up authenticate return r
def handle_421(self, r, **kwargs): """ Given a response, if is 421 parses out the Attestation-Challenge header and tries again with an attestation. Will try once. """ 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_421_calls = getattr(self, 'num_421_calls', 1) attestation_challenge = r.headers.get('attestation-challenge', '') if r.status_code == 421 and num_421_calls < 2: setattr(self, 'num_421_calls', num_421_calls + 1) pat = re.compile(r'^attestation ', flags=re.IGNORECASE) self.chal = parse_dict_header(pat.sub('', attestation_challenge, 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['Attestation'] = self.build_attestation_header(prep) _r = r.connection.send(prep, **kwargs) _r.history.append(r) _r.request = prep return _r setattr(self, 'num_421_calls', 1) return r
def test_parse_dict_header(value, expected): assert parse_dict_header(value) == expected