Skip to content

Commit

Permalink
Initial implementation of GitLab OIDC trusted publisher
Browse files Browse the repository at this point in the history
  • Loading branch information
facutuesca committed Feb 12, 2024
1 parent 9461adb commit b3a0d2e
Show file tree
Hide file tree
Showing 21 changed files with 2,129 additions and 170 deletions.
26 changes: 26 additions & 0 deletions tests/common/db/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
from warehouse.oidc.models import (
ActiveStatePublisher,
GitHubPublisher,
GitLabPublisher,
GooglePublisher,
PendingActiveStatePublisher,
PendingGitHubPublisher,
PendingGitLabPublisher,
PendingGooglePublisher,
)

Expand Down Expand Up @@ -51,6 +53,30 @@ class Meta:
added_by = factory.SubFactory(UserFactory)


class GitLabPublisherFactory(WarehouseFactory):
class Meta:
model = GitLabPublisher

id = factory.Faker("uuid4", cast_to=None)
project = factory.Faker("pystr", max_chars=12)
namespace = factory.Faker("pystr", max_chars=12)
workflow_filepath = "subfolder/example.yml"
environment = "production"


class PendingGitLabPublisherFactory(WarehouseFactory):
class Meta:
model = PendingGitLabPublisher

id = factory.Faker("uuid4", cast_to=None)
project_name = "fake-nonexistent-project"
project = factory.Faker("pystr", max_chars=12)
namespace = factory.Faker("pystr", max_chars=12)
workflow_filepath = "subfolder/example.yml"
environment = "production"
added_by = factory.SubFactory(UserFactory)


class GooglePublisherFactory(WarehouseFactory):
class Meta:
model = GooglePublisher
Expand Down
187 changes: 187 additions & 0 deletions tests/unit/accounts/test_views.py

Large diffs are not rendered by default.

96 changes: 93 additions & 3 deletions tests/unit/manage/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from warehouse.oidc.models import (
ActiveStatePublisher,
GitHubPublisher,
GitLabPublisher,
GooglePublisher,
OIDCPublisher,
)
Expand Down Expand Up @@ -5840,16 +5841,23 @@ def test_manage_project_oidc_publishers(self, monkeypatch):

view = views.ManageOIDCPublisherViews(project, request)
assert view.manage_project_oidc_publishers() == {
"disabled": {"GitHub": False, "Google": False, "ActiveState": False},
"disabled": {
"GitHub": False,
"GitLab": False,
"Google": False,
"ActiveState": False,
},
"project": project,
"github_publisher_form": view.github_publisher_form,
"gitlab_publisher_form": view.gitlab_publisher_form,
"google_publisher_form": view.google_publisher_form,
"activestate_publisher_form": view.activestate_publisher_form,
}

assert request.flags.disallow_oidc.calls == [
pretend.call(),
pretend.call(AdminFlagValue.DISALLOW_GITHUB_OIDC),
pretend.call(AdminFlagValue.DISALLOW_GITLAB_OIDC),
pretend.call(AdminFlagValue.DISALLOW_GOOGLE_OIDC),
pretend.call(AdminFlagValue.DISALLOW_ACTIVESTATE_OIDC),
]
Expand All @@ -5876,16 +5884,23 @@ def test_manage_project_oidc_publishers_admin_disabled(
view = views.ManageOIDCPublisherViews(project, pyramid_request)

assert view.manage_project_oidc_publishers() == {
"disabled": {"GitHub": True, "Google": True, "ActiveState": True},
"disabled": {
"GitHub": True,
"GitLab": True,
"Google": True,
"ActiveState": True,
},
"project": project,
"github_publisher_form": view.github_publisher_form,
"gitlab_publisher_form": view.gitlab_publisher_form,
"google_publisher_form": view.google_publisher_form,
"activestate_publisher_form": view.activestate_publisher_form,
}

assert pyramid_request.flags.disallow_oidc.calls == [
pretend.call(),
pretend.call(AdminFlagValue.DISALLOW_GITHUB_OIDC),
pretend.call(AdminFlagValue.DISALLOW_GITLAB_OIDC),
pretend.call(AdminFlagValue.DISALLOW_GOOGLE_OIDC),
pretend.call(AdminFlagValue.DISALLOW_ACTIVESTATE_OIDC),
]
Expand Down Expand Up @@ -5924,6 +5939,27 @@ def test_manage_project_oidc_publishers_admin_disabled(
normalized_environment=publisher.environment,
),
),
(
"add_gitlab_oidc_publisher",
pretend.stub(
id="fakeid",
publisher_name="GitLab",
project="fakerepo",
publisher_url=(
lambda x=None: "https://gitlab.com/fakeowner/fakerepo"
),
namespace="fakeowner",
workflow_filepath="subfolder/fakeworkflow.yml",
environment="some-environment",
),
lambda publisher: pretend.stub(
validate=pretend.call_recorder(lambda: True),
project=pretend.stub(data=publisher.project),
namespace=pretend.stub(data=publisher.namespace),
workflow_filepath=pretend.stub(data=publisher.workflow_filepath),
normalized_environment=publisher.environment,
),
),
(
"add_google_oidc_publisher",
pretend.stub(
Expand Down Expand Up @@ -6002,6 +6038,7 @@ def test_add_oidc_publisher_preexisting(
publisher_form_obj = make_form(publisher)
publisher_form_cls = pretend.call_recorder(lambda *a, **kw: publisher_form_obj)
monkeypatch.setattr(views, "GitHubPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GitLabPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GooglePublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "ActiveStatePublisherForm", publisher_form_cls)

Expand Down Expand Up @@ -6070,6 +6107,17 @@ def test_add_oidc_publisher_preexisting(
),
pretend.stub(publisher_name="GitHub"),
),
(
"add_gitlab_oidc_publisher",
pretend.stub(
validate=pretend.call_recorder(lambda: True),
project=pretend.stub(data="fakerepo"),
namespace=pretend.stub(data="fakeowner"),
workflow_filepath=pretend.stub(data="subfolder/fakeworkflow.yml"),
normalized_environment="some-environment",
),
pretend.stub(publisher_name="GitLab"),
),
(
"add_google_oidc_publisher",
pretend.stub(
Expand Down Expand Up @@ -6132,6 +6180,7 @@ def test_add_oidc_publisher_created(

publisher_form_cls = pretend.call_recorder(lambda *a, **kw: publisher_form_obj)
monkeypatch.setattr(views, "GitHubPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GitLabPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GooglePublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "ActiveStatePublisherForm", publisher_form_cls)
monkeypatch.setattr(
Expand Down Expand Up @@ -6223,6 +6272,24 @@ def test_add_oidc_publisher_created(
}
),
),
(
"add_gitlab_oidc_publisher",
"GitLab",
GitLabPublisher(
project="some-repository",
namespace="some-owner",
workflow_filepath="subfolder/some-workflow-filename.yml",
environment="some-environment",
),
MultiDict(
{
"namespace": "some-owner",
"project": "some-repository",
"workflow_filepath": "subfolder/some-workflow-filename.yml",
"environment": "some-environment",
}
),
),
(
"add_google_oidc_publisher",
"Google",
Expand Down Expand Up @@ -6310,9 +6377,15 @@ def test_add_oidc_publisher_already_registered_with_project(
)

assert getattr(view, view_name)() == {
"disabled": {"GitHub": False, "Google": False, "ActiveState": False},
"disabled": {
"GitHub": False,
"GitLab": False,
"Google": False,
"ActiveState": False,
},
"project": project,
"github_publisher_form": view.github_publisher_form,
"gitlab_publisher_form": view.gitlab_publisher_form,
"google_publisher_form": view.google_publisher_form,
"activestate_publisher_form": view.activestate_publisher_form,
}
Expand All @@ -6334,6 +6407,7 @@ def test_add_oidc_publisher_already_registered_with_project(
"view_name, publisher_name",
[
("add_github_oidc_publisher", "GitHub"),
("add_gitlab_oidc_publisher", "GitLab"),
("add_google_oidc_publisher", "Google"),
("add_activestate_oidc_publisher", "ActiveState"),
],
Expand Down Expand Up @@ -6383,6 +6457,7 @@ def test_add_oidc_publisher_ratelimited(
"view_name, publisher_name",
[
("add_github_oidc_publisher", "GitHub"),
("add_gitlab_oidc_publisher", "GitLab"),
("add_google_oidc_publisher", "Google"),
("add_activestate_oidc_publisher", "ActiveState"),
],
Expand Down Expand Up @@ -6425,6 +6500,7 @@ def test_add_oidc_publisher_admin_disabled(
"view_name, publisher_name",
[
("add_github_oidc_publisher", "GitHub"),
("add_gitlab_oidc_publisher", "GitLab"),
("add_google_oidc_publisher", "Google"),
("add_activestate_oidc_publisher", "ActiveState"),
],
Expand All @@ -6450,12 +6526,14 @@ def test_add_oidc_publisher_invalid_form(
)
publisher_form_cls = pretend.call_recorder(lambda *a, **kw: publisher_form_obj)
monkeypatch.setattr(views, "GitHubPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GitLabPublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "GooglePublisherForm", publisher_form_cls)
monkeypatch.setattr(views, "ActiveStatePublisherForm", publisher_form_cls)

view = views.ManageOIDCPublisherViews(project, request)
default_response = {
"github_publisher_form": publisher_form_obj,
"gitlab_publisher_form": publisher_form_obj,
"google_publisher_form": publisher_form_obj,
"activestate_publisher_form": publisher_form_obj,
}
Expand Down Expand Up @@ -6490,6 +6568,12 @@ def test_add_oidc_publisher_invalid_form(
workflow_filename="some-workflow-filename.yml",
environment="some-environment",
),
GitLabPublisher(
project="some-repository",
namespace="some-owner",
workflow_filepath="subfolder/some-workflow-filename.yml",
environment="some-environment",
),
GooglePublisher(
email="[email protected]",
sub="some-sub",
Expand Down Expand Up @@ -6598,6 +6682,12 @@ def test_delete_oidc_publisher_registered_to_multiple_projects(
workflow_filename="some-workflow-filename.yml",
environment="some-environment",
),
GitLabPublisher(
project="some-repository",
namespace="some-owner",
workflow_filepath="subfolder/some-workflow-filename.yml",
environment="some-environment",
),
GooglePublisher(
email="[email protected]",
sub="some-sub",
Expand Down
118 changes: 118 additions & 0 deletions tests/unit/oidc/forms/test_gitlab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pretend
import pytest
import wtforms

from webob.multidict import MultiDict

from warehouse.oidc.forms import gitlab


class TestPendingGitLabPublisherForm:
def test_validate(self, monkeypatch):
project_factory = []
data = MultiDict(
{
"namespace": "some-owner",
"project": "some-repo",
"workflow_filepath": "subfolder/some-workflow.yml",
"project_name": "some-project",
}
)
form = gitlab.PendingGitLabPublisherForm(
MultiDict(data), project_factory=project_factory
)

assert form._project_factory == project_factory
# We're testing only the basic validation here.
assert form.validate()

def test_validate_project_name_already_in_use(self):
project_factory = ["some-project"]
form = gitlab.PendingGitLabPublisherForm(project_factory=project_factory)

field = pretend.stub(data="some-project")
with pytest.raises(wtforms.validators.ValidationError):
form.validate_project_name(field)


class TestGitLabPublisherForm:
def test_validate(self):
data = MultiDict(
{
"namespace": "some-owner",
"project": "some-repo",
"workflow_filepath": "subfolder/some-workflow.yml",
}
)
form = gitlab.GitLabPublisherForm(MultiDict(data))

# We're testing only the basic validation here.
assert form.validate(), str(form.errors)

@pytest.mark.parametrize(
"data",
[
{"namespace": None, "project": "some", "workflow_filepath": "some"},
{"namespace": "", "project": "some", "workflow_filepath": "some"},
{
"namespace": "invalid_characters@",
"project": "some",
"workflow_filepath": "some",
},
{"project": None, "namespace": "some", "workflow_filepath": "some"},
{"project": "", "namespace": "some", "workflow_filepath": "some"},
{
"project": "$invalid#characters",
"namespace": "some",
"workflow_filepath": "some",
},
{"project": "some", "namespace": "some", "workflow_filepath": None},
{"project": "some", "namespace": "some", "workflow_filepath": ""},
],
)
def test_validate_basic_invalid_fields(self, monkeypatch, data):
form = gitlab.GitLabPublisherForm(MultiDict(data))

# We're testing only the basic validation here.
assert not form.validate()

@pytest.mark.parametrize(
"workflow_filepath",
[
"missing_suffix",
"/begin_slash.yml",
"end_with_slash.yml/",
"/begin/and/end/slash.yml/",
],
)
def test_validate_workflow_filepath(self, workflow_filepath):
form = gitlab.GitLabPublisherForm()
field = pretend.stub(data=workflow_filepath)

with pytest.raises(wtforms.validators.ValidationError):
form.validate_workflow_filepath(field)

@pytest.mark.parametrize(
"data, expected",
[
("", ""),
(" ", ""),
("\t\r\n", ""),
(None, ""),
],
)
def test_normalized_environment(self, data, expected):
form = gitlab.GitLabPublisherForm(environment=data)
assert form.normalized_environment == expected
Loading

0 comments on commit b3a0d2e

Please sign in to comment.