Skip to content
Merged
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
1 change: 0 additions & 1 deletion requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ redis>=2.8.0,<6.0.0
rfc3986
sentry-sdk
setuptools
sigstore~=3.5.1
pypi-attestations==0.0.16
sqlalchemy[asyncio]>=2.0,<3.0
stdlib-list
Expand Down
4 changes: 1 addition & 3 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2084,9 +2084,7 @@ sentry-sdk==2.18.0 \
sigstore==3.5.1 \
--hash=sha256:88f73c8edf1662ff9b86ef6fe0870bb6af4ac99ff808b84995e6a41957b7b3d2 \
--hash=sha256:e7023aef4e574120712c16c6bb151f4caee55791c4677fe30c92ef4e50800204
# via
# -r requirements/main.in
# pypi-attestations
# via pypi-attestations
sigstore-protobuf-specs==0.3.2 \
--hash=sha256:50c99fa6747a3a9c5c562a43602cf76df0b199af28f0e9d4319b6775630425ea \
--hash=sha256:cae041b40502600b8a633f43c257695d0222a94efa1e5110a7ec7ada78c39d99
Expand Down
9 changes: 2 additions & 7 deletions tests/unit/attestations/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import pretend
import pypi_attestations

from tests.common.db.oidc import GitHubPublisherFactory
from tests.common.db.packaging import FileFactory


def test_provenance_as_model(db_request, integrity_service, dummy_attestation):
db_request.oidc_publisher = pretend.stub(
publisher_name="GitHub",
repository="fake/fake",
workflow_filename="fake.yml",
environment="fake",
)
db_request.oidc_publisher = GitHubPublisherFactory.create()
file = FileFactory.create()
provenance = integrity_service.build_provenance(
db_request, file, [dummy_attestation]
Expand Down
100 changes: 29 additions & 71 deletions tests/unit/attestations/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
from pypi_attestations import (
Attestation,
AttestationType,
GitHubPublisher,
GitLabPublisher,
Provenance,
VerificationError,
)
from sigstore.verify import Verifier
from zope.interface.verify import verifyClass

from tests.common.db.oidc import GitHubPublisherFactory, GitLabPublisherFactory
from tests.common.db.oidc import (
ActiveStatePublisherFactory,
GitHubPublisherFactory,
GitLabPublisherFactory,
GooglePublisherFactory,
)
from tests.common.db.packaging import FileFactory
from warehouse.attestations import IIntegrityService, services
from warehouse.attestations.errors import AttestationUploadError
Expand All @@ -38,12 +41,7 @@ def test_interface_matches(self):
assert verifyClass(IIntegrityService, services.NullIntegrityService)

def test_build_provenance(self, db_request, dummy_attestation):
db_request.oidc_publisher = pretend.stub(
publisher_name="GitHub",
repository="fake/fake",
workflow_filename="fake.yml",
environment="fake",
)
db_request.oidc_publisher = GitHubPublisherFactory.create()

file = FileFactory.create()
service = services.NullIntegrityService.create_service(None, db_request)
Expand Down Expand Up @@ -88,16 +86,27 @@ def test_parse_attestations_fails_no_publisher(self, db_request):
):
integrity_service.parse_attestations(db_request, pretend.stub())

def test_parse_attestations_fails_unsupported_publisher(self, db_request):
@pytest.mark.parametrize(
"publisher_factory",
[
GitLabPublisherFactory,
GooglePublisherFactory,
ActiveStatePublisherFactory,
],
)
def test_parse_attestations_fails_unsupported_publisher(
self, db_request, publisher_factory
):
integrity_service = services.IntegrityService(
metrics=pretend.stub(), session=db_request.db
)
db_request.oidc_publisher = pretend.stub(
supports_attestations=False, publisher_name="fake"
)
db_request.oidc_publisher = publisher_factory.create()
with pytest.raises(
AttestationUploadError,
match="Attestations are not currently supported with fake publishers",
match=(
"Attestations are not currently supported with "
f"{db_request.oidc_publisher.publisher_name} publishers"
),
):
integrity_service.parse_attestations(db_request, pretend.stub())

Expand All @@ -107,7 +116,7 @@ def test_parse_attestations_fails_malformed_attestation(self, metrics, db_reques
session=db_request.db,
)

db_request.oidc_publisher = pretend.stub(supports_attestations=True)
db_request.oidc_publisher = pretend.stub(attestation_identity=pretend.stub())
db_request.POST["attestations"] = "{'malformed-attestation'}"
with pytest.raises(
AttestationUploadError,
Expand All @@ -128,7 +137,7 @@ def test_parse_attestations_fails_multiple_attestations(
session=db_request.db,
)

db_request.oidc_publisher = pretend.stub(supports_attestations=True)
db_request.oidc_publisher = pretend.stub(attestation_identity=pretend.stub())
db_request.POST["attestations"] = TypeAdapter(list[Attestation]).dump_json(
[dummy_attestation, dummy_attestation]
)
Expand Down Expand Up @@ -173,8 +182,7 @@ def test_parse_attestations_fails_verification(
)

db_request.oidc_publisher = pretend.stub(
supports_attestations=True,
publisher_verification_policy=pretend.call_recorder(lambda c: None),
attestation_identity=pretend.stub(),
)
db_request.oidc_claims = {"sha": "somesha"}
db_request.POST["attestations"] = TypeAdapter(list[Attestation]).dump_json(
Expand Down Expand Up @@ -204,8 +212,7 @@ def test_parse_attestations_fails_wrong_predicate(
session=db_request.db,
)
db_request.oidc_publisher = pretend.stub(
supports_attestations=True,
publisher_verification_policy=pretend.call_recorder(lambda c: None),
attestation_identity=pretend.stub(),
)
db_request.oidc_claims = {"sha": "somesha"}
db_request.POST["attestations"] = TypeAdapter(list[Attestation]).dump_json(
Expand Down Expand Up @@ -240,8 +247,7 @@ def test_parse_attestations_succeeds(
session=db_request.db,
)
db_request.oidc_publisher = pretend.stub(
supports_attestations=True,
publisher_verification_policy=pretend.call_recorder(lambda c: None),
attestation_identity=pretend.stub(),
)
db_request.oidc_claims = {"sha": "somesha"}
db_request.POST["attestations"] = TypeAdapter(list[Attestation]).dump_json(
Expand All @@ -259,31 +265,9 @@ def test_parse_attestations_succeeds(
)
assert attestations == [dummy_attestation]

def test_build_provenance_fails_unsupported_publisher(
self, metrics, db_request, dummy_attestation
):
integrity_service = services.IntegrityService(
metrics=metrics,
session=db_request.db,
)

db_request.oidc_publisher = pretend.stub(publisher_name="not-existing")

file = FileFactory.create()
with pytest.raises(AttestationUploadError, match="Unsupported publisher"):
integrity_service.build_provenance(db_request, file, [dummy_attestation])

# If building provenance fails, nothing is stored or associated with the file
assert not file.provenance

assert metrics.increment.calls == []

@pytest.mark.parametrize(
"publisher_factory",
[
GitHubPublisherFactory,
GitLabPublisherFactory,
],
[GitHubPublisherFactory],
)
def test_build_provenance_succeeds(
self, metrics, db_request, publisher_factory, dummy_attestation
Expand Down Expand Up @@ -311,32 +295,6 @@ def test_build_provenance_succeeds(
]


def test_publisher_from_oidc_publisher_succeeds_github(db_request):
publisher = GitHubPublisherFactory.create()

attestation_publisher = services._publisher_from_oidc_publisher(publisher)
assert isinstance(attestation_publisher, GitHubPublisher)
assert attestation_publisher.repository == publisher.repository
assert attestation_publisher.workflow == publisher.workflow_filename
assert attestation_publisher.environment == publisher.environment


def test_publisher_from_oidc_publisher_succeeds_gitlab(db_request):
publisher = GitLabPublisherFactory.create()

attestation_publisher = services._publisher_from_oidc_publisher(publisher)
assert isinstance(attestation_publisher, GitLabPublisher)
assert attestation_publisher.repository == publisher.project_path
assert attestation_publisher.environment == publisher.environment


def test_publisher_from_oidc_publisher_fails_unsupported():
publisher = pretend.stub(publisher_name="not-existing")

with pytest.raises(AttestationUploadError):
services._publisher_from_oidc_publisher(publisher)


def test_extract_attestations_from_request_empty_list(db_request):
db_request.oidc_publisher = GitHubPublisherFactory.create()
db_request.POST = {"attestations": json.dumps([])}
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/oidc/models/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ def test_oidc_publisher_not_default_verifiable(self):
publisher.check_claims_existence(signed_claims={})
assert str(e.value) == "No required verifiable claims"

def test_supports_attestations(self):
def test_attestation_identity(self):
publisher = _core.OIDCPublisher(projects=[])
assert not publisher.supports_attestations
assert not publisher.attestation_identity

@pytest.mark.parametrize(
("url", "publisher_url", "expected"),
Expand Down
45 changes: 19 additions & 26 deletions tests/unit/oidc/models/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from tests.common.db.oidc import GitHubPublisherFactory, PendingGitHubPublisherFactory
from warehouse.oidc import errors
from warehouse.oidc.errors import InvalidPublisherError
from warehouse.oidc.models import _core, github


Expand Down Expand Up @@ -602,31 +601,6 @@ def test_github_publisher_environment_claim(self, truth, claim, valid):
check = github.GitHubPublisher.__optional_verifiable_claims__["environment"]
assert check(truth, claim, pretend.stub()) is valid

@pytest.mark.parametrize(
("ref", "sha", "raises"),
[
("ref", "sha", False),
(None, "sha", False),
("ref", None, False),
(None, None, True),
],
)
def test_github_publisher_verification_policy(self, ref, sha, raises):
publisher = github.GitHubPublisher(
repository_name="fakerepo",
repository_owner="fakeowner",
repository_owner_id="fakeid",
workflow_filename="fakeworkflow.yml",
environment="",
)
claims = {"ref": ref, "sha": sha}

if not raises:
publisher.publisher_verification_policy(claims)
else:
with pytest.raises(InvalidPublisherError):
publisher.publisher_verification_policy(claims)

def test_github_publisher_duplicates_cant_be_created(self, db_request):
publisher1 = github.GitHubPublisher(
repository_name="repository_name",
Expand Down Expand Up @@ -676,6 +650,25 @@ def test_github_publisher_verify_url(self, url, expected):
)
assert publisher.verify_url(url) == expected

@pytest.mark.parametrize("environment", ["", "some-env"])
def test_github_publisher_attestation_identity(self, environment):
publisher = github.GitHubPublisher(
repository_name="repository_name",
repository_owner="repository_owner",
repository_owner_id="666",
workflow_filename="workflow_filename.yml",
environment=environment,
)

identity = publisher.attestation_identity
assert identity.repository == publisher.repository
assert identity.workflow == publisher.workflow_filename

if not environment:
assert identity.environment is None
else:
assert identity.environment == publisher.environment


class TestPendingGitHubPublisher:
def test_reify_does_not_exist_yet(self, db_request):
Expand Down
Loading