def wrapper(image_id, *args, **kwargs): response_data = get_data() image_repos = response_data['images'].get(image_id) if image_repos is None: raise exceptions.HTTPError(httplib.NOT_FOUND) found_match = False # Check if an uprotected repo matches the request # Check if a protected repo matches the request cert = _get_certificate() repo_tuple = None for repo_id in image_repos: repo_tuple = response_data['repos'].get(repo_id) # if the repo is unprotected or the path is supported if not repo_tuple.protected: found_match = True break elif cert and cert.check_path(repo_tuple.url_path): found_match = True break if not found_match: # return 404 so we don't reveal the existence of images that the user # is not authorized for raise exceptions.HTTPError(httplib.NOT_FOUND) return func(image_id, repo_tuple, *args, **kwargs)
def test_filter_nonauthorized_result(self, mock_name_authorized, mock_is_authorized): result = base.SearchResult('rhel', 'Red Hat Enterprise Linux', **base.SearchResult.result_defaults) mock_is_authorized.side_effect = exceptions.HTTPError(httplib.NOT_FOUND) mock_name_authorized.side_effect = exceptions.HTTPError(httplib.NOT_FOUND) ret = self.backend._filter_result(result) self.assertIs(ret, False) mock_is_authorized.assert_called_once_with(result.name)
def test_filter_non_authorized_result_v2(self, mock_v2_is_authorized, mock_v1_authorized): result = base.SearchResult('rhel', 'Red Hat Enterprise Linux', True, True, 8, True) mock_v1_authorized.side_effect = exceptions.HTTPError(mock.Mock(status=404), 'not found') mock_v2_is_authorized.side_effect = exceptions.HTTPError(mock.Mock(status=404), 'not found') ret = self.backend._filter_result(result) self.assertIs(ret, False) mock_v2_is_authorized.assert_called_once_with(result.name)
def validate_and_transform_repo_name(path): """ Checks and extracts a repo registry id from the path parameter. The repo name is considered to be the substring left of any of the [tags, manifests, blobs]. :param path: value for full path component containing both repo name and file path :type path: basestring :return: tuple containing extracted name and path components :rtype: tuple """ path_components = ['tags', 'manifests', 'blobs'] name_component = '' path_component = '' if not any([value in path for value in path_components]): raise exceptions.HTTPError(httplib.NOT_FOUND) for component in path_components: if component in path: name_component = path.split(component, 1)[0].strip('/') path_component = component + path.split(component, 1)[1] return name_component, path_component
def get_image_file_path(image_id, repo_info, filename): """ Return the file path for a file in an image :param image_id: The identifier for the image :type image_id: basestring :param repo_info: The tuple containing the information about the repository :type repo_info: crane.data.Repo :param filename: The identifier for the file belonging to the image :type filename: basestring :returns: file path for a file inside an image :rtype: tuple :raises NotFoundException: if the file specified is not known """ if filename not in VALID_IMAGE_FILES: raise exceptions.HTTPError(httplib.NOT_FOUND) if filename == 'layer': mediatype = 'application/octet-stream' else: mediatype = 'application/json' base_path = current_app.config.get(config.KEY_SC_CONTENT_DIR_V1) result = os.path.join(base_path, repo_info.repository, image_id, filename) return result, mediatype
def validate_and_transform_repo_name(path): """ Checks and extracts a repo registry id from the path parameter. The repo name is considered to be the substring left of any of the [tags, manifests, blobs]. :param path: value for full path component containing both repo name and file path :type path: basestring :return: tuple containing extracted name, path components and the identified component type :rtype: tuple """ component_types = ['tags', 'manifests', 'blobs'] components = path.split('/') for rindex, component in enumerate(reversed(components)): if component in component_types: split_index = len(components) - 1 - rindex name_component = '/'.join(components[:split_index]) path_component = '/'.join(components[split_index:]) return name_component, path_component, component raise exceptions.HTTPError(httplib.NOT_FOUND)
def test_workflow_filter_false_with_image_repository_document_kind( self, mock_is_authorized, mock_name_authorized): """ When the should_filter attribute of SearchResult instance is True, the base implementation of the filter_results should be called and the return value should be based on the value returned/exception raised by the mocked app_util.repo_is_authorized """ mock_is_authorized.side_effect = exceptions.HTTPError( httplib.NOT_FOUND) mock_name_authorized.side_effect = exceptions.HTTPError( httplib.NOT_FOUND) result = SearchResult('rhel', 'Red Hat Enterprise Linux', False, False, 0, True) ret_val = self.solr._filter_result(result) self.assertEquals(ret_val, False)
def test_workflow_filter_true_with_image_repository_with_defaults( self, mock_is_authorized, mock_name_is_authorised): """ When the should_filter attribute of SearchResult instance is default which is False, the base implementation of the filter_results is not called """ mock_name_is_authorised.side_effect = exceptions.HTTPError( mock.Mock(status=404), 'not found') result = SearchResult('rhel', 'Red Hat Enterprise Linux', **SearchResult.result_defaults) ret_val = self.solr._filter_result(result) self.assertEquals(ret_val, True)
def repo_is_authorized(repo_id): """ determines if the current request is authorized to read the given repo ID. :param repo_id: name of the repository being accessed :type repo_id: basestring :raises exceptions.HTTPError: if authorization fails 403: if the user is not authorized 404: if the repo does not exist in this app """ response_data = get_data() repo_tuple = response_data['repos'].get(repo_id) # if this deployment of this app does not know about the requested repo if repo_tuple is None: raise exceptions.HTTPError(httplib.NOT_FOUND) if repo_tuple.protected: cert = _get_certificate() if not cert or not cert.check_path(repo_tuple.url_path): # return 404 so we don't reveal the existence of repos that the user # is not authorized for raise exceptions.HTTPError(httplib.NOT_FOUND)
def validate_and_transform_repoid(repo_id): """ Validates that the repo ID does not contain more than one slash, and removes the default "library" namespace if present. :param repo_id: unique ID for the repository. May contain 0 or 1 of the "/" character. For repo IDs that do not contain a slash, the docker client currently prepends "library/" when making this call. This function strips that off. :type repo_id: basestring :return: repo ID without the "library" namespace :rtype: basestring """ # a valid repository ID will have zero or one slash if len(repo_id.split('/')) > 2: raise exceptions.HTTPError(httplib.NOT_FOUND) # for repositories that do not have a "/" in the name, docker will add # "library/" to the beginning of the repository path. if repo_id.startswith('library/'): return repo_id[len('library/'):] return repo_id
def get_image_file_url(image_id, repo_info, filename): """ Return the url for a file in an image :param image_id: The identifier for the image :type image_id: basestring :param repo_info: The tuple containing the information about the repository :type repo_info: crane.data.Repo :param filename: The identifier for the file belonging to the image :type filename: basestring :returns: url for a file inside an image :rtype: str :raises NotFoundException: if the file specified is not known """ if filename not in VALID_IMAGE_FILES: raise exceptions.HTTPError(httplib.NOT_FOUND) base_url = repo_info.url if not base_url.endswith('/'): base_url += '/' return urlparse.urljoin(base_url, '/'.join((image_id, filename)))
def test_custom_message(self): string_value, http_code = app_util.http_error_handler( exceptions.HTTPError(httplib.BAD_GATEWAY, 'Foo Error')) self.assertEquals(string_value, 'Foo Error') self.assertEquals(http_code, httplib.BAD_GATEWAY)
def test_default_message(self): string_value, http_code = app_util.http_error_handler( exceptions.HTTPError(httplib.NOT_FOUND)) self.assertEquals(string_value, httplib.responses[httplib.NOT_FOUND]) self.assertEquals(http_code, httplib.NOT_FOUND)
def name_serve_or_redirect(relative_path): """ Redirects the client to the path from where the file can be accessed. If 'serve_content' is set to true use send_file to provide the requested file directly, taking into account the 'content_dir_v2' parameter. :param relative_path: the relative path after /v2/. :type relative_path: basestring :return: 302 redirect response :rtype: flask.Response """ components = app_util.validate_and_transform_repo_name(relative_path) name_component, path_component, component_type = components base_url = repository.get_path_for_repo(name_component) if not base_url.endswith('/'): base_url += '/' schema2_data = repository.get_schema2_data_for_repo(name_component) used_mediatype = 'application/json' if component_type != 'blobs' else 'application/octet-stream' if component_type == 'manifests' and schema2_data is not None: manifest_list_data = repository.get_manifest_list_data_for_repo( name_component) manifest_list_amd64_tags = repository.get_manifest_list_amd64_for_repo( name_component) if schema2_data: schema2_data = json.loads(schema2_data) if manifest_list_data: manifest_list_data = json.loads(manifest_list_data) if manifest_list_amd64_tags: manifest_list_amd64_tags = json.loads(manifest_list_amd64_tags) manifest, identifier = path_component.split('/') if schema2_data or manifest_list_data: # if it is a newer docker client it sets accept headers to manifest schema 1, 2 and list # if it is an older docker client, he doesnot set any of accept headers accept_headers = get_accept_headers(request) schema2_mediatype = 'application/vnd.docker.distribution.manifest.v2+json' manifest_list_mediatype = 'application/vnd.docker.distribution.manifest.list.v2+json' # check first manifest list type if manifest_list_mediatype in accept_headers and identifier in manifest_list_data: path_component = os.path.join(manifest, 'list', identifier) used_mediatype = manifest_list_mediatype # this is needed for older clients which do not understand manifest list elif identifier in manifest_list_amd64_tags.keys(): if schema2_mediatype in accept_headers: schema_version = manifest_list_amd64_tags[identifier][1] if schema_version == 2: used_mediatype = schema2_mediatype path_component = os.path.join( manifest, str(schema_version), manifest_list_amd64_tags[identifier][0]) elif manifest_list_amd64_tags[identifier][1] == 1: path_component = os.path.join( manifest, '1', manifest_list_amd64_tags[identifier][0]) # this is needed in case when there is no amd64 image manifest, but there are within # one repo manifest list and image manifest with the same tag else: path_component = os.path.join(manifest, '1', identifier) elif schema2_mediatype in accept_headers and identifier in schema2_data: path_component = os.path.join(manifest, '2', identifier) used_mediatype = schema2_mediatype else: path_component = os.path.join(manifest, '1', identifier) # this is needed for V3Repo which do not have schema2 manifests else: path_component = os.path.join(manifest, '1', identifier) serve_content = current_app.config.get(config.KEY_SC_ENABLE) if serve_content: base_path = current_app.config.get(config.KEY_SC_CONTENT_DIR_V2) repo_name = repository.get_pulp_repository_name(name_component) result = os.path.join(base_path, repo_name, path_component) try: return send_file(result, mimetype=used_mediatype, add_etags=False) except OSError: raise exceptions.HTTPError(httplib.NOT_FOUND) else: url = base_url + path_component # perform CDN rewrites and auth url = cdn_rewrite_redirect_url(url) url = cdn_auth_token_url(url) return redirect(url)