Exemple #1
0
    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)
Exemple #2
0
    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)
Exemple #3
0
    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)
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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)
Exemple #7
0
 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)
Exemple #8
0
 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)
Exemple #9
0
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)
Exemple #10
0
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
Exemple #11
0
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)))
Exemple #12
0
 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)
Exemple #13
0
 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)
Exemple #14
0
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)