Skip to content

feat(job_token_scope): support Groups in job token allowlist API #2816

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

Merged
merged 8 commits into from
May 13, 2024
50 changes: 50 additions & 0 deletions docs/gl_objects/job_token_scope.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,53 @@ Refresh the current state of job token scope::
scope.refresh()
print(scope.inbound_enabled)
# False

Get a project's CI/CD job token inbound allowlist::

allowlist = scope.allowlist.list()

Add a project to the project's inbound allowlist::

allowed_project = scope.allowlist.create({"target_project_id": 42})

Remove a project from the project's inbound allowlist::

allowed_project.delete()
# or directly using a project ID
scope.allowlist.delete(42)

.. warning::

Similar to above, the ID attributes you receive from the create and list
APIs are not consistent (in create() the id is returned as ``source_project_id`` whereas list() returns as ``id``). To safely retrieve the ID of the allowlisted project
regardless of how the object was created, always use its ``.get_id()`` method.

Using ``.get_id()``::

resp = allowlist.create({"target_project_id": 2})
allowlist_id = resp.get_id()

allowlists = project.allowlist.list()
for allowlist in allowlists:
allowlist_id == allowlist.get_id()

Get a project's CI/CD job token inbound groups allowlist::

allowlist = scope.groups_allowlist.list()

Add a project to the project's inbound groups allowlist::

allowed_project = scope.groups_allowlist.create({"target_project_id": 42})

Remove a project from the project's inbound agroups llowlist::

allowed_project.delete()
# or directly using a Group ID
scope.groups_allowlist.delete(42)

.. warning::

Similar to above, the ID attributes you receive from the create and list
APIs are not consistent. To safely retrieve the ID of the allowlisted group
regardless of how the object was created, always use its ``.get_id()`` method.

48 changes: 48 additions & 0 deletions gitlab/v4/objects/job_token_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

from gitlab.base import RESTManager, RESTObject
from gitlab.mixins import (
CreateMixin,
DeleteMixin,
GetWithoutIdMixin,
ListMixin,
ObjectDeleteMixin,
RefreshMixin,
SaveMixin,
UpdateMethod,
UpdateMixin,
)
from gitlab.types import RequiredOptional

__all__ = [
"ProjectJobTokenScope",
Expand All @@ -18,6 +23,9 @@
class ProjectJobTokenScope(RefreshMixin, SaveMixin, RESTObject):
_id_attr = None

allowlist: "AllowlistProjectManager"
groups_allowlist: "AllowlistGroupManager"


class ProjectJobTokenScopeManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
_path = "/projects/{project_id}/job_token_scope"
Expand All @@ -27,3 +35,43 @@ class ProjectJobTokenScopeManager(GetWithoutIdMixin, UpdateMixin, RESTManager):

def get(self, **kwargs: Any) -> ProjectJobTokenScope:
return cast(ProjectJobTokenScope, super().get(**kwargs))


class AllowlistProject(ObjectDeleteMixin, RESTObject):
_id_attr = "target_project_id" # note: only true for create endpoint

def get_id(self) -> int:
"""Returns the id of the resource. This override deals with
the fact that either an `id` or a `target_project_id` attribute
is returned by the server depending on the endpoint called."""
target_project_id = cast(int, super().get_id())
if target_project_id is not None:
return target_project_id
return cast(int, self.id)


class AllowlistProjectManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
_path = "/projects/{project_id}/job_token_scope/allowlist"
_obj_cls = AllowlistProject
_from_parent_attrs = {"project_id": "project_id"}
_create_attrs = RequiredOptional(required=("target_project_id",))


class AllowlistGroup(ObjectDeleteMixin, RESTObject):
_id_attr = "target_group_id" # note: only true for create endpoint

def get_id(self) -> int:
"""Returns the id of the resource. This override deals with
the fact that either an `id` or a `target_group_id` attribute
is returned by the server depending on the endpoint called."""
target_group_id = cast(int, super().get_id())
if target_group_id is not None:
return target_group_id
return cast(int, self.id)


class AllowlistGroupManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
_path = "/projects/{project_id}/job_token_scope/groups_allowlist"
_obj_cls = AllowlistGroup
_from_parent_attrs = {"project_id": "project_id"}
_create_attrs = RequiredOptional(required=("target_group_id",))
116 changes: 116 additions & 0 deletions tests/functional/api/test_project_job_token_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#allow-any-project-to-access-your-project
def test_enable_limit_access_to_this_project(gl, project):
scope = project.job_token_scope.get()

scope.enabled = True
scope.save()

scope.refresh()

assert scope.inbound_enabled


def test_disable_limit_access_to_this_project(gl, project):
scope = project.job_token_scope.get()

scope.enabled = False
scope.save()

scope.refresh()

assert not scope.inbound_enabled


def test_add_project_to_job_token_scope_allowlist(gl, project):
project_to_add = gl.projects.create({"name": "Ci_Cd_token_add_proj"})

scope = project.job_token_scope.get()
resp = scope.allowlist.create({"target_project_id": project_to_add.id})

assert resp.source_project_id == project.id
assert resp.target_project_id == project_to_add.id

project_to_add.delete()


def test_projects_job_token_scope_allowlist_contains_added_project_name(gl, project):
scope = project.job_token_scope.get()
project_name = "Ci_Cd_token_named_proj"
project_to_add = gl.projects.create({"name": project_name})
scope.allowlist.create({"target_project_id": project_to_add.id})

scope.refresh()
assert any(allowed.name == project_name for allowed in scope.allowlist.list())

project_to_add.delete()


def test_remove_project_by_id_from_projects_job_token_scope_allowlist(gl, project):
scope = project.job_token_scope.get()

project_to_add = gl.projects.create({"name": "Ci_Cd_token_remove_proj"})

scope.allowlist.create({"target_project_id": project_to_add.id})

scope.refresh()

scope.allowlist.delete(project_to_add.id)

scope.refresh()
assert not any(
allowed.id == project_to_add.id for allowed in scope.allowlist.list()
)

project_to_add.delete()


def test_add_group_to_job_token_scope_allowlist(gl, project):
group_to_add = gl.groups.create(
{"name": "add_group", "path": "allowlisted-add-test"}
)

scope = project.job_token_scope.get()
resp = scope.groups_allowlist.create({"target_group_id": group_to_add.id})

assert resp.source_project_id == project.id
assert resp.target_group_id == group_to_add.id

group_to_add.delete()


def test_projects_job_token_scope_groups_allowlist_contains_added_group_name(
gl, project
):
scope = project.job_token_scope.get()
group_name = "list_group"
group_to_add = gl.groups.create(
{"name": group_name, "path": "allowlisted-add-and-list-test"}
)

scope.groups_allowlist.create({"target_group_id": group_to_add.id})

scope.refresh()
assert any(allowed.name == group_name for allowed in scope.groups_allowlist.list())

group_to_add.delete()


def test_remove_group_by_id_from_projects_job_token_scope_groups_allowlist(gl, project):
scope = project.job_token_scope.get()

group_to_add = gl.groups.create(
{"name": "delete_group", "path": "allowlisted-delete-test"}
)

scope.groups_allowlist.create({"target_group_id": group_to_add.id})

scope.refresh()

scope.groups_allowlist.delete(group_to_add.id)

scope.refresh()
assert not any(
allowed.name == group_to_add.name for allowed in scope.groups_allowlist.list()
)

group_to_add.delete()
Loading