def _get_path(self, path, headers=None): """ Retrieve a single path within the upstream registry, and return a 2-tuple of the headers and the response body. :param path: a full http path to retrieve that will be urljoin'd to the upstream registry url. :type path: basestring :param headers: headers sent in the request :type headers: dict :return: (headers, response body) :rtype: tuple """ url = urlparse.urljoin(self.registry_url, path) _logger.debug(_('Retrieving {0}'.format(url))) request = DownloadRequest(url, StringIO()) request.headers = headers if self.token: request.headers = auth_util.update_token_auth_header( request.headers, self.token) report = self.downloader.download_one(request) # If the download was unauthorized, check report header, if basic auth is expected # retry with basic auth, otherwise attempt to get a token and try again if report.state == report.DOWNLOAD_FAILED: if report.error_report.get( 'response_code') == httplib.UNAUTHORIZED: auth_header = report.headers.get('www-authenticate') if auth_header is None: raise IOError("401 responses are expected to " "contain authentication information") elif "Basic" in auth_header: _logger.debug( _('Download unauthorized, retrying with basic authentication' )) report = self.auth_downloader.download_one(request) else: _logger.debug( _('Download unauthorized, attempting to retrieve a token.' )) self.token = auth_util.request_token( self.auth_downloader, request, auth_header, self.name) request.headers = auth_util.update_token_auth_header( request.headers, self.token) report = self.downloader.download_one(request) if report.state == report.DOWNLOAD_FAILED: # this condition was added in case the registry would not allow to access v2 endpoint # but still token would be valid for other endpoints. # see https://pulp.plan.io/issues/2643 if path == '/v2/' and report.error_report.get( 'response_code') == httplib.UNAUTHORIZED: pass else: self._raise_path_error(report) return report.headers, report.destination.getvalue()
def test_no_headers(self): """ Test that when there are no existing headers, it is added. """ mock_headers = auth_util.update_token_auth_header(None, "mock token") self.assertDictEqual(mock_headers, {"Authorization": "Bearer mock token"})
def test_with_headers(self): """ Test that when the headers exists, the auth token is added to it. """ updated = auth_util.update_token_auth_header({"mock": "header"}, "mock token") self.assertDictEqual(updated, { "Authorization": "Bearer mock token", "mock": "header" })
def download_failed(self, report): """ If the download is unauthorized, depending on the returned auth scheme, either try with basic auth or attempt to retrieve a token and try again. :param report: download report :type report: nectar.report.DownloadReport """ if report.error_report.get('response_code') == httplib.UNAUTHORIZED: request = self._requests_map[report.url] auth_header = report.headers.get('www-authenticate') if auth_header is None: raise IOError( "401 responses are expected to contain authentication information" ) if "Basic" in auth_header: self.downloader.session.headers = auth_util.update_basic_auth_header( self.downloader.session.headers, self.basic_auth_username, self.basic_auth_password) _logger.debug( _('Download unauthorized, retrying with basic authentication' )) else: token = auth_util.request_token( self.parent.index_repository.auth_downloader, request, auth_header, self.parent.index_repository.name) self.downloader.session.headers = auth_util.update_token_auth_header( self.downloader.session.headers, token) _logger.debug( "Download unauthorized, retrying with new bearer token.") # Events must be false or download_failed will recurse report = self.downloader.download_one(request, events=False) if report.state is report.DOWNLOAD_SUCCEEDED: self.download_succeeded(report) elif report.state is report.DOWNLOAD_FAILED: super(AuthDownloadStep, self).download_failed(report) # Docker blobs have ancestry relationships and need all blobs to function. Sync should # stop immediately to prevent publishing of an incomplete repository. os.kill(os.getpid(), signal.SIGKILL)