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
15 changes: 15 additions & 0 deletions tests/unit/oidc/models/test_activestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ def test_stored_claims(self):

assert publisher.stored_claims() == {}

def test_admin_details(self):
publisher = ActiveStatePublisher(
organization="fakeorg",
activestate_project_name="fakeproject",
actor="fakeactor",
actor_id="fakeactorid",
)

assert publisher.admin_details == [
("Organization", "fakeorg"),
("Project", "fakeproject"),
("Actor", "fakeactor"),
("Actor ID", "fakeactorid"),
]

def test_stringifies_as_project_url(self):
org_name = "fakeorg"
project_name = "fakeproject"
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/oidc/models/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def test_attestation_identity(self):
publisher = _core.OIDCPublisher(projects=[])
assert not publisher.attestation_identity

def test_admin_details_default(self):
publisher = _core.OIDCPublisher(projects=[])
assert publisher.admin_details == []

@pytest.mark.parametrize(
("url", "publisher_url", "expected"),
[
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/oidc/models/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,37 @@ def test_github_publisher_computed_properties(self):
"ref": "someref",
}

def test_github_publisher_admin_details_with_environment(self):
publisher = github.GitHubPublisher(
repository_name="fakerepo",
repository_owner="fakeowner",
repository_owner_id="fakeid",
workflow_filename="fakeworkflow.yml",
environment="fakeenv",
)

assert publisher.admin_details == [
("Repository", "fakeowner/fakerepo"),
("Workflow", "fakeworkflow.yml"),
("Owner ID", "fakeid"),
("Environment", "fakeenv"),
]

def test_github_publisher_admin_details_without_environment(self):
publisher = github.GitHubPublisher(
repository_name="fakerepo",
repository_owner="fakeowner",
repository_owner_id="fakeid",
workflow_filename="fakeworkflow.yml",
environment="",
)

assert publisher.admin_details == [
("Repository", "fakeowner/fakerepo"),
("Workflow", "fakeworkflow.yml"),
("Owner ID", "fakeid"),
]

def test_github_publisher_unaccounted_claims(self, monkeypatch):
scope = pretend.stub()
sentry_sdk = pretend.stub(
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/oidc/models/test_gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,33 @@ def test_gitlab_publisher_computed_properties(self):
"ref_path": "someref",
}

def test_gitlab_publisher_admin_details_with_environment(self):
publisher = gitlab.GitLabPublisher(
project="fakerepo",
namespace="fakeowner",
workflow_filepath="subfolder/fakeworkflow.yml",
environment="fakeenv",
)

assert publisher.admin_details == [
("Project", "fakeowner/fakerepo"),
("Workflow", "subfolder/fakeworkflow.yml"),
("Environment", "fakeenv"),
]

def test_gitlab_publisher_admin_details_without_environment(self):
publisher = gitlab.GitLabPublisher(
project="fakerepo",
namespace="fakeowner",
workflow_filepath="subfolder/fakeworkflow.yml",
environment="",
)

assert publisher.admin_details == [
("Project", "fakeowner/fakerepo"),
("Workflow", "subfolder/fakeworkflow.yml"),
]

def test_gitlab_publisher_unaccounted_claims(self, monkeypatch):
scope = pretend.stub()
sentry_sdk = pretend.stub(
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/oidc/models/test_google.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ def test_stringifies_as_email(self):

assert str(publisher) == publisher.email

def test_google_publisher_admin_details_with_sub(self):
publisher = google.GooglePublisher(
email="fake@example.com",
sub="fakesubject",
)

assert publisher.admin_details == [
("Email", "fake@example.com"),
("Subject", "fakesubject"),
]

def test_google_publisher_admin_details_without_sub(self):
publisher = google.GooglePublisher(
email="fake@example.com",
sub=None,
)

assert publisher.admin_details == [
("Email", "fake@example.com"),
]

def test_google_publisher_all_known_claims(self):
assert google.GooglePublisher.all_known_claims() == {
# verifiable claims
Expand Down
68 changes: 41 additions & 27 deletions warehouse/admin/templates/admin/projects/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ <h3 class="card-title">
<td></td>
</tr>
<tr>
<td rowspan="2" style="vertical-align: top;">Upload limit</td>
<td rowspan="2">Upload limit</td>
<td colspan="3">
{# Calculate effective limit #}
{% set effective_upload_limit = MAX_FILESIZE %}
Expand All @@ -142,16 +142,16 @@ <h3 class="card-title">
{% set effective_upload_limit = project.organization.upload_limit %}
{% set limit_source = "organization" %}
{% endif %}
<div class="alert alert-info" style="margin-bottom: 10px;">

<div class="alert alert-info">
<strong>Effective limit: {{ effective_upload_limit|filesizeformat(binary=True) }}</strong> (from {{ limit_source }})
</div>
<table class="table table-sm table-bordered" style="margin-bottom: 0;">

<table class="table table-sm table-bordered">
<tr>
<td style="width: 40%;">System default:</td>
<td style="width: 30%;">{{ MAX_FILESIZE|filesizeformat(binary=True) }}</td>
<td style="width: 30%;"><small class="text-muted">Not configurable</small></td>
<td>System default:</td>
<td>{{ MAX_FILESIZE|filesizeformat(binary=True) }}</td>
<td><small class="text-muted">Not configurable</small></td>
</tr>
<tr>
<td>Project limit:</td>
Expand Down Expand Up @@ -193,7 +193,7 @@ <h3 class="card-title">
{% else %}
{% set upload_limit_value = '' %}
{% endif %}
<input type="number" name="upload_limit" id="uploadLimit" class="form-control form-control-sm mr-2" min="{{ MAX_FILESIZE / ONE_MIB }}" max="{{ UPLOAD_LIMIT_CAP / ONE_MIB }}" step=1 value="{{ upload_limit_value|int }}" style="width: 100px;">
<input type="number" name="upload_limit" id="uploadLimit" class="form-control form-control-sm mr-2" min="{{ MAX_FILESIZE / ONE_MIB }}" max="{{ UPLOAD_LIMIT_CAP / ONE_MIB }}" step=1 value="{{ upload_limit_value|int }}">
<span class="mr-3">MiB</span>
<button type="submit" class="btn btn-primary btn-sm">Update</button>
{% if project.upload_limit and limit_source != "project" %}
Expand All @@ -204,7 +204,7 @@ <h3 class="card-title">
</form>
</tr>
<tr>
<td rowspan="2" style="vertical-align: top;">Total size limit</td>
<td rowspan="2">Total size limit</td>
<td colspan="3">
{# Calculate effective limit #}
{% set effective_total_size_limit = MAX_PROJECT_SIZE %}
Expand All @@ -217,16 +217,16 @@ <h3 class="card-title">
{% set effective_total_size_limit = project.organization.total_size_limit %}
{% set size_limit_source = "organization" %}
{% endif %}
<div class="alert alert-info" style="margin-bottom: 10px;">

<div class="alert alert-info">
<strong>Effective limit: {{ effective_total_size_limit|filesizeformat(binary=True) }}</strong> (from {{ size_limit_source }})
</div>
<table class="table table-sm table-bordered" style="margin-bottom: 0;">

<table class="table table-sm table-bordered">
<tr>
<td style="width: 40%;">System default:</td>
<td style="width: 30%;">{{ MAX_PROJECT_SIZE|filesizeformat(binary=True) }}</td>
<td style="width: 30%;"><small class="text-muted">Not configurable</small></td>
<td>System default:</td>
<td>{{ MAX_PROJECT_SIZE|filesizeformat(binary=True) }}</td>
<td><small class="text-muted">Not configurable</small></td>
</tr>
<tr>
<td>Project limit:</td>
Expand Down Expand Up @@ -268,7 +268,7 @@ <h3 class="card-title">
{% else %}
{% set total_size_limit_value = '' %}
{% endif %}
<input type="number" name="total_size_limit" id="totalSizeLimit" class="form-control form-control-sm mr-2" min="{{ MAX_PROJECT_SIZE // ONE_GIB }}" value="{{total_size_limit_value}}" style="width: 100px;">
<input type="number" name="total_size_limit" id="totalSizeLimit" class="form-control form-control-sm mr-2" min="{{ MAX_PROJECT_SIZE // ONE_GIB }}" value="{{total_size_limit_value}}">
<span class="mr-3">GiB</span>
<button type="submit" class="btn btn-primary btn-sm">Update</button>
{% if project.total_size_limit and size_limit_source != "project" %}
Expand Down Expand Up @@ -480,21 +480,35 @@ <h3 class="card-title">Releases</h3>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Publisher name</th>
<th>Publisher</th>
<th>Configuration</th>
<th>URL</th>
<th>repr</th>
</tr>
</thead>
<tbody>
{% for pub in oidc_publishers %}
<tr>
<td>{{ pub.publisher_name }}</td>
{% if pub.publisher_url() %}
<td><a href="{{ pub.publisher_url() }}">{{ pub.publisher_url() }}</a></td>
{% else %}
<td>N/A</td>
{% endif %}
<td><code>{{ pub }}</code></td>
<td><strong>{{ pub.publisher_name }}</strong></td>
<td>
{% if pub.admin_details %}
<dl class="mb-0">
{% for label, value in pub.admin_details %}
<dt class="small d-inline-block">{{ label }}:</dt>
<dd class="small d-inline"><code>{{ value }}</code></dd>
<br>
{% endfor %}
</dl>
{% else %}
<code>{{ pub }}</code>
{% endif %}
</td>
<td>
{% if pub.publisher_url() %}
<a href="{{ pub.publisher_url() }}" target="_blank">{{ pub.publisher_url() }}</a>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Expand Down
8 changes: 8 additions & 0 deletions warehouse/oidc/models/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ def exists(self, session) -> bool: # pragma: no cover
# Only concrete subclasses are constructed.
raise NotImplementedError

@property
def admin_details(self) -> list[tuple[str, str]]:
"""
Returns a list of (label, value) tuples for display in admin interface.
Each publisher should override this to provide its configuration details.
"""
return []


class OIDCPublisher(OIDCPublisherMixin, db.Model):
__tablename__ = "oidc_publishers"
Expand Down
10 changes: 10 additions & 0 deletions warehouse/oidc/models/activestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ def exists(self, session) -> bool:
)
).scalar()

@property
def admin_details(self) -> list[tuple[str, str]]:
"""Returns ActiveState publisher configuration details for admin display."""
return [
("Organization", self.organization),
("Project", self.activestate_project_name),
("Actor", self.actor),
("Actor ID", self.actor_id),
]

@classmethod
def lookup_by_claims(cls, session, signed_claims: SignedClaims) -> Self:
query: Query = Query(cls).filter_by(
Expand Down
12 changes: 12 additions & 0 deletions warehouse/oidc/models/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ def exists(self, session) -> bool:
)
).scalar()

@property
def admin_details(self) -> list[tuple[str, str]]:
"""Returns GitHub publisher configuration details for admin display."""
details = [
("Repository", self.repository),
("Workflow", self.workflow_filename),
("Owner ID", self.repository_owner_id),
]
if self.environment:
details.append(("Environment", self.environment))
return details


class GitHubPublisher(GitHubPublisherMixin, OIDCPublisher):
__tablename__ = "github_oidc_publishers"
Expand Down
11 changes: 11 additions & 0 deletions warehouse/oidc/models/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,17 @@ def exists(self, session) -> bool:
)
).scalar()

@property
def admin_details(self) -> list[tuple[str, str]]:
"""Returns GitLab publisher configuration details for admin display."""
details = [
("Project", self.project_path),
("Workflow", self.workflow_filepath),
]
if self.environment:
details.append(("Environment", self.environment))
return details


class GitLabPublisher(GitLabPublisherMixin, OIDCPublisher):
__tablename__ = "gitlab_oidc_publishers"
Expand Down
10 changes: 10 additions & 0 deletions warehouse/oidc/models/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ def exists(self, session) -> bool:
)
).scalar()

@property
def admin_details(self) -> list[tuple[str, str]]:
"""Returns Google publisher configuration details for admin display."""
details = [
("Email", self.email),
]
if self.sub:
details.append(("Subject", self.sub))
return details


class GooglePublisher(GooglePublisherMixin, OIDCPublisher):
__tablename__ = "google_oidc_publishers"
Expand Down