Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): add /v1experimental/importfindings/{source} API endpoint for linter findings #2987

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions docs/api/get-v1-importfindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
layout: page
title: GET /v1experimental/importfindings
permalink: /get-v1-importfindings/
parent: API
nav_order: 5
---
# GET /v1experimental/importfindings/{source}
Experimental
{: .label }

Given a specific OSV.dev source, report any records that are failing import-time quality checks.

{: .no_toc }

<details open markdown="block">
<summary>
Table of contents
</summary>
{: .text-delta }
- TOC
{:toc}
</details>

## Experimental endpoint

This API endpoint is still considered experimental. It is targeted to operators
of home databases that OSV.dev imports from. We would value any and all
feedback. If you give this a try, please consider [opening an
issue](https://github.com/google/osv.dev/issues/new) and letting us know about
any pain points or highlights.

## Purpose

The purpose of this endpoint is give OSV record providers (home database
operators) a machine-readable way to reason about records they have published that
do not meet [OSV.dev's quality bar](data_quality.html) (and therefore have not been imported).

## Parameters

The only parameter you need for this API call is the source, in order to construct the URL.

`https://api.osv.dev/v1/importfindings/{source}`

The `source` value is the same as the `name` value in [`source.yaml`](https://github.com/google/osv.dev/blob/master/source.yaml)

Case Sensitivity: API requests are case-sensitive. Please ensure that you use the correct case for parameter names and values. For example, use 'ghsa' instead of 'GHSA'.

## Request sample

```bash
curl "https://api.osv.dev/v1experimental/importfindings/example"
```

## Example 200 response

```
{"invalid_records":[{"bug_id":"EX-1234","source":"example","findings":["IMPORT_FINDING_TYPE_INVALID_JSON"],"first_seen":"2024-12-19T15:18:00.945105Z","last_attempt":"2024-12-19T15:18:00.945105Z"}]}
```
10 changes: 7 additions & 3 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@ nav_order: 2
# API (1.0)

## Download the OpenAPI specification

[Download Here](https://osv.dev/docs/osv_service_v1.swagger.json){: .btn .btn-purple}

## OSV API

### Want a quick example?
Please see the [quickstart](api-quickstart.md).

Please see the [quickstart](api-quickstart.md).

### How does the API work?

There are four different types of requests that can be made of the API.
There are five different types of requests that can be made of the API.

1. Query vulnerabilities for a particular project at a given [commit hash or version](post-v1-query.md).
2. [Batched query vulnerabilities](post-v1-querybatch.md) for given package versions and commit hashes.
3. Return a `Vulnerability` object for a given [OSV ID](get-v1-vulns.md).
4. Return a list of [probable versions](post-v1-determineversion.md) of a specified C/C++ project. (**Experimental**)
5. Retrieve [records failing import-time quality checks](get-v1-importfindings.md), by record source (**Experimental**)

### Is the API rate limited?
Currently there are no limits on the API.

Currently there are no limits on the API.
73 changes: 40 additions & 33 deletions gcp/api/osv_service_v1_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions gcp/api/osv_service_v1_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import osv.importfinding_pb2
import osv.vulnerability_pb2
import sys
import typing
Expand Down Expand Up @@ -203,6 +204,23 @@ class DetermineVersionParameters(google.protobuf.message.Message):

global___DetermineVersionParameters = DetermineVersionParameters

@typing.final
class ImportFindingsParameters(google.protobuf.message.Message):
"""Parameters for ImportFindings."""

DESCRIPTOR: google.protobuf.descriptor.Descriptor

SOURCE_FIELD_NUMBER: builtins.int
source: builtins.str
def __init__(
self,
*,
source: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["source", b"source"]) -> None: ...

global___ImportFindingsParameters = ImportFindingsParameters

@typing.final
class VersionQuery(google.protobuf.message.Message):
"""The version query."""
Expand Down Expand Up @@ -280,6 +298,24 @@ class VersionMatchList(google.protobuf.message.Message):

global___VersionMatchList = VersionMatchList

@typing.final
class ImportFindingList(google.protobuf.message.Message):
"""Result of ImportFindings."""

DESCRIPTOR: google.protobuf.descriptor.Descriptor

INVALID_RECORDS_FIELD_NUMBER: builtins.int
@property
def invalid_records(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[osv.importfinding_pb2.ImportFinding]: ...
def __init__(
self,
*,
invalid_records: collections.abc.Iterable[osv.importfinding_pb2.ImportFinding] | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["invalid_records", b"invalid_records"]) -> None: ...

global___ImportFindingList = ImportFindingList

@typing.final
class VersionMatch(google.protobuf.message.Message):
"""Match information for the provided VersionQuery."""
Expand Down
44 changes: 44 additions & 0 deletions gcp/api/osv_service_v1_pb2_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ def __init__(self, channel):
request_serializer=osv__service__v1__pb2.DetermineVersionParameters.SerializeToString,
response_deserializer=osv__service__v1__pb2.VersionMatchList.FromString,
_registered_method=True)
self.ImportFindings = channel.unary_unary(
'/osv.v1.OSV/ImportFindings',
request_serializer=osv__service__v1__pb2.ImportFindingsParameters.SerializeToString,
response_deserializer=osv__service__v1__pb2.ImportFindingList.FromString,
_registered_method=True)


class OSVServicer(object):
Expand Down Expand Up @@ -98,6 +103,13 @@ def DetermineVersion(self, request, context):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def ImportFindings(self, request, context):
"""Get import findings per source.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_OSVServicer_to_server(servicer, server):
rpc_method_handlers = {
Expand All @@ -121,6 +133,11 @@ def add_OSVServicer_to_server(servicer, server):
request_deserializer=osv__service__v1__pb2.DetermineVersionParameters.FromString,
response_serializer=osv__service__v1__pb2.VersionMatchList.SerializeToString,
),
'ImportFindings': grpc.unary_unary_rpc_method_handler(
servicer.ImportFindings,
request_deserializer=osv__service__v1__pb2.ImportFindingsParameters.FromString,
response_serializer=osv__service__v1__pb2.ImportFindingList.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'osv.v1.OSV', rpc_method_handlers)
Expand Down Expand Up @@ -240,3 +257,30 @@ def DetermineVersion(request,
timeout,
metadata,
_registered_method=True)

@staticmethod
def ImportFindings(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/osv.v1.OSV/ImportFindings',
osv__service__v1__pb2.ImportFindingsParameters.SerializeToString,
osv__service__v1__pb2.ImportFindingList.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
21 changes: 21 additions & 0 deletions gcp/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,27 @@ def DetermineVersion(self, request, context: grpc.ServicerContext):
res = determine_version(request.query, context).result()
return res

@ndb_context
@trace_filter.log_trace
def ImportFindings(self, request, context: grpc.ServicerContext):
"""Return a list of `ImportFinding` for a given source."""
source = request.source
# TODO(gongh@): add source check,
# check if the source name exists in the source repository.
if not source:
context.abort(grpc.StatusCode.INVALID_ARGUMENT,
'Missing Source: Please specify the source')
if get_gcp_project() == _TEST_INSTANCE:
logging.info('Checking import finding for %s\n', source)

query = osv.ImportFinding.query(osv.ImportFinding.source == source)
import_findings: list[osv.ImportFinding] = query.fetch()
invalid_records = []
for finding in import_findings:
invalid_records.append(finding.to_proto())

return osv_service_v1_pb2.ImportFindingList(invalid_records=invalid_records)

@ndb_context
def Check(self, request, context: grpc.ServicerContext):
"""Health check per the gRPC health check protocol."""
Expand Down
Binary file modified gcp/api/v1/api_descriptor.pb
Binary file not shown.
Loading
Loading