Skip to content

Commit

Permalink
feat: add support for SAML group links (#2367)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhiandthetruth committed Nov 21, 2022
1 parent 65abb85 commit 1020ce9
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 1 deletion.
22 changes: 22 additions & 0 deletions docs/gl_objects/groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,28 @@ You can use the ``ldapgroups`` manager to list available LDAP groups::
# list the groups for a specific LDAP provider
ldap_groups = gl.ldapgroups.list(search='foo', provider='ldapmain')

SAML group links
================

Add a SAML group link to an existing GitLab group::

saml_link = group.saml_group_links.create({
"saml_group_name": "<your_saml_group_name>",
"access_level": <chosen_access_level>
})

List a group's SAML group links::

group.saml_group_links.list()

Get a SAML group link::

group.saml_group_links.get("<your_saml_group_name>")

Remove a link::

saml_link.delete()

Groups hooks
============

Expand Down
21 changes: 21 additions & 0 deletions gitlab/v4/objects/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CRUDMixin,
DeleteMixin,
ListMixin,
NoUpdateMixin,
ObjectDeleteMixin,
SaveMixin,
)
Expand Down Expand Up @@ -58,6 +59,8 @@
"GroupLDAPGroupLinkManager",
"GroupSubgroup",
"GroupSubgroupManager",
"GroupSAMLGroupLink",
"GroupSAMLGroupLinkManager",
]


Expand Down Expand Up @@ -98,6 +101,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
subgroups: "GroupSubgroupManager"
variables: GroupVariableManager
wikis: GroupWikiManager
saml_group_links: "GroupSAMLGroupLinkManager"

@cli.register_custom_action("Group", ("project_id",))
@exc.on_http_error(exc.GitlabTransferProjectError)
Expand Down Expand Up @@ -464,3 +468,20 @@ class GroupLDAPGroupLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager
_create_attrs = RequiredOptional(
required=("provider", "group_access"), exclusive=("cn", "filter")
)


class GroupSAMLGroupLink(ObjectDeleteMixin, RESTObject):
_id_attr = "name"
_repr_attr = "name"


class GroupSAMLGroupLinkManager(NoUpdateMixin, RESTManager):
_path = "/groups/{group_id}/saml_group_links"
_obj_cls: Type[GroupSAMLGroupLink] = GroupSAMLGroupLink
_from_parent_attrs = {"group_id": "id"}
_create_attrs = RequiredOptional(required=("saml_group_name", "access_level"))

def get(
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
) -> GroupSAMLGroupLink:
return cast(GroupSAMLGroupLink, super().get(id=id, lazy=lazy, **kwargs))
8 changes: 8 additions & 0 deletions tests/functional/api/test_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,11 @@ def test_group_transfer(gl, group):

transferred_group = gl.groups.get(transfer_group.id)
assert transferred_group.path == transferred_group.full_path


@pytest.mark.gitlab_premium
@pytest.mark.xfail(reason="need to setup an identity provider or it's mock")
def test_group_saml_group_links(group):
group.saml_group_links.create(
{"saml_group_name": "saml-group-1", "access_level": 10}
)
114 changes: 113 additions & 1 deletion tests/unit/objects/test_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
import responses

import gitlab
from gitlab.v4.objects import GroupDescendantGroup, GroupLDAPGroupLink, GroupSubgroup
from gitlab.v4.objects import (
GroupDescendantGroup,
GroupLDAPGroupLink,
GroupSAMLGroupLink,
GroupSubgroup,
)
from gitlab.v4.objects.projects import GroupProject, SharedProject

content = {"name": "name", "id": 1, "path": "path"}
Expand All @@ -20,6 +25,11 @@
"filter": "(memberOf=cn=some_group,ou=groups,ou=fake_ou,dc=sub_dc,dc=example,dc=tld)",
}
]
saml_group_links_content = [{"name": "saml-group-1", "access_level": 10}]
create_saml_group_link_request_body = {
"saml_group_name": "saml-group-1",
"access_level": 10,
}
projects_content = [
{
"id": 9,
Expand Down Expand Up @@ -237,6 +247,75 @@ def resp_list_ldap_group_links(no_content):
yield rsps


@pytest.fixture
def resp_list_saml_group_links():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/groups/1/saml_group_links",
json=saml_group_links_content,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_get_saml_group_link():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/groups/1/saml_group_links/saml-group-1",
json=saml_group_links_content[0],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_create_saml_group_link():
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.POST,
url="http://localhost/api/v4/groups/1/saml_group_links",
match=[
responses.matchers.json_params_matcher(
create_saml_group_link_request_body
)
],
json=saml_group_links_content[0],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_delete_saml_group_link(no_content):
with responses.RequestsMock() as rsps:
rsps.add(
method=responses.POST,
url="http://localhost/api/v4/groups/1/saml_group_links",
match=[
responses.matchers.json_params_matcher(
create_saml_group_link_request_body
)
],
json=saml_group_links_content[0],
content_type="application/json",
status=200,
)
rsps.add(
method=responses.DELETE,
url="http://localhost/api/v4/groups/1/saml_group_links/saml-group-1",
json=no_content,
content_type="application/json",
status=204,
)
yield rsps


def test_get_group(gl, resp_groups):
data = gl.groups.get(1)
assert isinstance(data, gitlab.v4.objects.Group)
Expand Down Expand Up @@ -341,3 +420,36 @@ def test_update_group_push_rule(
def test_delete_group_push_rule(group, resp_delete_push_rules_group):
pr = group.pushrules.get()
pr.delete()


def test_list_saml_group_links(group, resp_list_saml_group_links):
saml_group_links = group.saml_group_links.list()
assert isinstance(saml_group_links[0], GroupSAMLGroupLink)
assert saml_group_links[0].name == saml_group_links_content[0]["name"]
assert (
saml_group_links[0].access_level == saml_group_links_content[0]["access_level"]
)


def test_get_saml_group_link(group, resp_get_saml_group_link):
saml_group_link = group.saml_group_links.get("saml-group-1")
assert isinstance(saml_group_link, GroupSAMLGroupLink)
assert saml_group_link.name == saml_group_links_content[0]["name"]
assert saml_group_link.access_level == saml_group_links_content[0]["access_level"]


def test_create_saml_group_link(group, resp_create_saml_group_link):
saml_group_link = group.saml_group_links.create(create_saml_group_link_request_body)
assert isinstance(saml_group_link, GroupSAMLGroupLink)
assert (
saml_group_link.name == create_saml_group_link_request_body["saml_group_name"]
)
assert (
saml_group_link.access_level
== create_saml_group_link_request_body["access_level"]
)


def test_delete_saml_group_link(group, resp_delete_saml_group_link):
saml_group_link = group.saml_group_links.create(create_saml_group_link_request_body)
saml_group_link.delete()

0 comments on commit 1020ce9

Please sign in to comment.