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
20 changes: 10 additions & 10 deletions warehouse/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ msgstr ""
msgid "Select project"
msgstr ""

#: warehouse/manage/forms.py:507 warehouse/oidc/forms/_core.py:24
#: warehouse/manage/forms.py:507 warehouse/oidc/forms/_core.py:36
#: warehouse/oidc/forms/gitlab.py:47
msgid "Specify project name"
msgstr ""
Expand Down Expand Up @@ -676,45 +676,45 @@ msgstr ""
msgid "Expired invitation for '${username}' deleted."
msgstr ""

#: warehouse/oidc/forms/_core.py:26 warehouse/oidc/forms/_core.py:37
#: warehouse/oidc/forms/_core.py:38 warehouse/oidc/forms/_core.py:49
#: warehouse/oidc/forms/gitlab.py:50 warehouse/oidc/forms/gitlab.py:54
msgid "Invalid project name"
msgstr ""

#: warehouse/oidc/forms/_core.py:55
#: warehouse/oidc/forms/_core.py:68
#, python-brace-format
msgid ""
"This project already exists: use the project's publishing settings <a "
"href='${url}'>here</a> to create a Trusted Publisher for it."
msgstr ""

#: warehouse/oidc/forms/_core.py:64
#: warehouse/oidc/forms/_core.py:77
msgid "This project already exists."
msgstr ""

#: warehouse/oidc/forms/_core.py:69
#: warehouse/oidc/forms/_core.py:82
msgid "This project name isn't allowed"
msgstr ""

#: warehouse/oidc/forms/_core.py:73
#: warehouse/oidc/forms/_core.py:86
msgid "This project name is too similar to an existing project"
msgstr ""

#: warehouse/oidc/forms/_core.py:78
#: warehouse/oidc/forms/_core.py:91
msgid ""
"This project name isn't allowed (conflict with the Python standard "
"library module name)"
msgstr ""

#: warehouse/oidc/forms/_core.py:106 warehouse/oidc/forms/_core.py:117
#: warehouse/oidc/forms/_core.py:119 warehouse/oidc/forms/_core.py:130
msgid "Specify a publisher ID"
msgstr ""

#: warehouse/oidc/forms/_core.py:107 warehouse/oidc/forms/_core.py:118
#: warehouse/oidc/forms/_core.py:120 warehouse/oidc/forms/_core.py:131
msgid "Publisher must be specified by ID"
msgstr ""

#: warehouse/oidc/forms/_core.py:123
#: warehouse/oidc/forms/_core.py:136
msgid "Specify an environment name"
msgstr ""

Expand Down
14 changes: 7 additions & 7 deletions warehouse/macaroons/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,14 @@ def verify(self, raw_macaroon: str, request, context, permission) -> bool:

def create_macaroon(
self,
location,
description,
scopes,
location: str,
description: str,
scopes: list[caveats.Caveat],
*,
user_id=None,
oidc_publisher_id=None,
additional=None,
):
user_id: uuid.UUID | None = None,
oidc_publisher_id: str | None = None,
additional: dict[str, typing.Any] | None = None,
) -> tuple[str, Macaroon]:
"""
Returns a tuple of a new raw (serialized) macaroon and its DB model.
The description provided is not embedded into the macaroon, only stored
Expand Down
8 changes: 7 additions & 1 deletion warehouse/oidc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations

import typing

from celery.schedules import crontab

Expand All @@ -12,8 +15,11 @@
GOOGLE_OIDC_ISSUER_URL,
)

if typing.TYPE_CHECKING:
from pyramid.config import Configurator


def includeme(config):
def includeme(config: Configurator) -> None:
oidc_publisher_service_class = config.maybe_dotted(
config.registry.settings["oidc.backend"]
)
Expand Down
17 changes: 15 additions & 2 deletions warehouse/oidc/forms/_core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import typing

import markupsafe
import structlog
import wtforms
Expand All @@ -15,10 +19,18 @@
)
from warehouse.utils.project import PROJECT_NAME_RE

if typing.TYPE_CHECKING:
from warehouse.accounts.models import User

log = structlog.get_logger()


class PendingPublisherMixin:
# Attributes that must be provided by subclasses
_user: User
_check_project_name: typing.Callable[[str], None]
_route_url: typing.Callable[..., str]

project_name = wtforms.StringField(
validators=[
wtforms.validators.InputRequired(message=_("Specify project name")),
Expand All @@ -28,7 +40,7 @@ class PendingPublisherMixin:
]
)

def validate_project_name(self, field):
def validate_project_name(self, field: wtforms.Field) -> None:
project_name = field.data

try:
Expand All @@ -39,7 +51,8 @@ def validate_project_name(self, field):
# If the user owns the existing project, the error message includes a
# link to the project settings that the user can modify.
if self._user in e.existing_project.owners:
url_params = {name: value for name, value in self.data.items() if value}
# Mixin doesn't inherit from wtforms.Form but composed classes do
url_params = {name: value for name, value in self.data.items() if value} # type: ignore[attr-defined] # noqa: E501
url_params["provider"] = {self.provider}
url = self._route_url(
"manage.project.settings.publishing",
Expand Down
8 changes: 4 additions & 4 deletions warehouse/oidc/forms/activestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class GqlResponse(TypedDict):
errors: list[dict[str, Any]]


def _no_double_dashes(form, field):
def _no_double_dashes(_form: wtforms.Form, field: wtforms.Field) -> None:
if _DOUBLE_DASHES.search(field.data):
raise wtforms.validators.ValidationError(
_("Double dashes are not allowed in the name")
)


def _no_leading_or_trailing_dashes(form, field):
def _no_leading_or_trailing_dashes(_form: wtforms.Form, field: wtforms.Field) -> None:
if field.data.startswith("-") or field.data.endswith("-"):
raise wtforms.validators.ValidationError(
_("Leading or trailing dashes are not allowed in the name")
Expand Down Expand Up @@ -150,7 +150,7 @@ def process_org_response(response: GqlResponse) -> None:
_GRAPHQL_GET_ORGANIZATION, {"orgname": org_url_name}, process_org_response
)

def validate_organization(self, field):
def validate_organization(self, field: wtforms.Field) -> None:
self._lookup_organization(field.data)

def _lookup_actor(self, actor: str) -> UserResponse:
Expand All @@ -170,7 +170,7 @@ def process_actor_response(response: GqlResponse) -> UserResponse:
_GRAPHQL_GET_ACTOR, {"username": actor}, process_actor_response
)

def validate_actor(self, field):
def validate_actor(self, field: wtforms.Field) -> None:
actor = field.data

actor_info = self._lookup_actor(actor)
Expand Down
14 changes: 7 additions & 7 deletions warehouse/oidc/forms/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ class GitHubPublisherBase(wtforms.Form):
# https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
environment = wtforms.StringField(validators=[wtforms.validators.Optional()])

def __init__(self, *args, api_token, **kwargs):
def __init__(self, *args, api_token: str, **kwargs):
super().__init__(*args, **kwargs)
self._api_token = api_token

def _headers_auth(self):
def _headers_auth(self) -> dict[str, str]:
if not self._api_token:
return {}
return {"Authorization": f"token {self._api_token}"}

def _lookup_owner(self, owner):
def _lookup_owner(self, owner: str) -> dict[str, str | int]:
# To actually validate the owner, we ask GitHub's API about them.
# We can't do this for the repository, since it might be private.
try:
Expand Down Expand Up @@ -113,7 +113,7 @@ def _lookup_owner(self, owner):

return response.json()

def validate_owner(self, field):
def validate_owner(self, field: wtforms.Field) -> None:
owner = field.data

# We pre-filter owners with a regex, to avoid loading GitHub's API
Expand All @@ -129,7 +129,7 @@ def validate_owner(self, field):
self.normalized_owner = owner_info["login"]
self.owner_id = owner_info["id"]

def validate_workflow_filename(self, field):
def validate_workflow_filename(self, field: wtforms.Field) -> None:
workflow_filename = field.data

if not (
Expand All @@ -144,7 +144,7 @@ def validate_workflow_filename(self, field):
_("Workflow filename must be a filename only, without directories")
)

def validate_environment(self, field):
def validate_environment(self, field: wtforms.Field) -> None:
environment = field.data

if not environment:
Expand Down Expand Up @@ -174,7 +174,7 @@ def validate_environment(self, field):
)

@property
def normalized_environment(self):
def normalized_environment(self) -> str:
# The only normalization is due to case-insensitivity.
#
# NOTE: We explicitly do not compare `self.environment.data` to None,
Expand Down
4 changes: 2 additions & 2 deletions warehouse/oidc/forms/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class GitLabPublisherBase(wtforms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def validate_workflow_filepath(self, field):
def validate_workflow_filepath(self, field: wtforms.Field) -> None:
workflow_filepath = field.data

if not (
Expand All @@ -91,7 +91,7 @@ def validate_workflow_filepath(self, field):
)

@property
def normalized_environment(self):
def normalized_environment(self) -> str:
# NOTE: We explicitly do not compare `self.environment.data` to None,
# since it might also be falsey via an empty string (or might be
# only whitespace, which we also treat as a None case).
Expand Down
12 changes: 7 additions & 5 deletions warehouse/oidc/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
from warehouse.rate_limiting.interfaces import RateLimiterException

if TYPE_CHECKING:
from warehouse.oidc.models import PendingOIDCPublisher
from warehouse.oidc.models import OIDCPublisher, PendingOIDCPublisher
from warehouse.packaging.models import Project

SignedClaims = NewType("SignedClaims", dict[str, Any])
SignedClaims = NewType("SignedClaims", dict[str, Any]) # TODO: narrow this down


class IOIDCPublisherService(Interface):
def verify_jwt_signature(unverified_token: str):
def verify_jwt_signature(unverified_token: str) -> SignedClaims | None:
"""
Verify the given JWT's signature, returning its signed claims if
valid. If the signature is invalid, `None` is returned.
Expand All @@ -26,7 +26,9 @@ def verify_jwt_signature(unverified_token: str):
"""
pass

def find_publisher(signed_claims: SignedClaims, *, pending: bool = False):
def find_publisher(
signed_claims: SignedClaims, *, pending: bool = False
) -> OIDCPublisher | PendingOIDCPublisher | None:
"""
Given a mapping of signed claims produced by `verify_jwt_signature`,
attempt to find and return either a `OIDCPublisher` or `PendingOIDCPublisher`
Expand All @@ -38,7 +40,7 @@ def find_publisher(signed_claims: SignedClaims, *, pending: bool = False):

def reify_pending_publisher(
pending_publisher: PendingOIDCPublisher, project: Project
):
) -> OIDCPublisher:
"""
Reify the given pending `PendingOIDCPublisher` into an `OIDCPublisher`,
adding it to the given project (presumed newly created) in the process.
Expand Down
Loading