From 0e173e4b7eb7ed93627947e13155322dded79773 Mon Sep 17 00:00:00 2001 From: Abhishek Singh Date: Tue, 8 Nov 2022 01:00:03 +0530 Subject: [PATCH 1/4] feat: add saml group links --- gitlab/v4/objects/groups.py | 16 ++++++++++++++++ tests/functional/api/test_groups.py | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index 4111496ed..d6d600dbc 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -12,6 +12,7 @@ CRUDMixin, DeleteMixin, ListMixin, + NoUpdateMixin, ObjectDeleteMixin, SaveMixin, ) @@ -58,6 +59,8 @@ "GroupLDAPGroupLinkManager", "GroupSubgroup", "GroupSubgroupManager", + "GroupSAMLGroupLink", + "GroupSAMLGroupLinkManager", ] @@ -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) @@ -464,3 +468,15 @@ 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")) diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py index 51fbe3272..82cde5181 100644 --- a/tests/functional/api/test_groups.py +++ b/tests/functional/api/test_groups.py @@ -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 +def test_group_saml_group_links(gl): + group = gl.groups.create({"name": "gitlab-test-group", "path": "gitlab-test-group"}) + group.saml_group_links.create( + {"saml_group_name": "saml-group-1", "access_level": 10} + ) From 87a2105b3264f12b8c5da515a5ab93f5e36f5e29 Mon Sep 17 00:00:00 2001 From: Abhishek Singh Date: Tue, 8 Nov 2022 01:34:51 +0530 Subject: [PATCH 2/4] feat: add get to saml group links --- gitlab/v4/objects/groups.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index d6d600dbc..d03eb38c8 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -480,3 +480,8 @@ class GroupSAMLGroupLinkManager(NoUpdateMixin, RESTManager): _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)) From 863ea11e2fbea3273c9177eb04df00a37bac6044 Mon Sep 17 00:00:00 2001 From: Abhishek Singh Date: Sun, 20 Nov 2022 18:58:03 +0530 Subject: [PATCH 3/4] feat: add tests/docs for saml group links --- docs/gl_objects/groups.rst | 22 ++++++ tests/functional/api/test_groups.py | 1 + tests/unit/objects/test_groups.py | 114 +++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst index 98f0beeb6..fafa40a86 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -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": "", + "access_level": + }) + +List a group's SAML group links:: + + group.saml_group_links.list() + +Get a SAML group link:: + + group.saml_group_links.get("") + +Remove a link:: + + saml_link.delete() + Groups hooks ============ diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py index 82cde5181..bc3b6ef0b 100644 --- a/tests/functional/api/test_groups.py +++ b/tests/functional/api/test_groups.py @@ -307,6 +307,7 @@ def test_group_transfer(gl, group): @pytest.mark.gitlab_premium +@pytest.mark.xfail(reason="need to setup an identity provider or it's mock") def test_group_saml_group_links(gl): group = gl.groups.create({"name": "gitlab-test-group", "path": "gitlab-test-group"}) group.saml_group_links.create( diff --git a/tests/unit/objects/test_groups.py b/tests/unit/objects/test_groups.py index 29e3c1a77..58c350827 100644 --- a/tests/unit/objects/test_groups.py +++ b/tests/unit/objects/test_groups.py @@ -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"} @@ -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, @@ -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) @@ -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() From 3405a2567757e48f9ccc84b15ca89157a2e19d37 Mon Sep 17 00:00:00 2001 From: Abhishek Singh Date: Sun, 20 Nov 2022 21:05:38 +0530 Subject: [PATCH 4/4] feat: modify functional tests saml group add --- tests/functional/api/test_groups.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py index bc3b6ef0b..ec381d594 100644 --- a/tests/functional/api/test_groups.py +++ b/tests/functional/api/test_groups.py @@ -308,8 +308,7 @@ def test_group_transfer(gl, group): @pytest.mark.gitlab_premium @pytest.mark.xfail(reason="need to setup an identity provider or it's mock") -def test_group_saml_group_links(gl): - group = gl.groups.create({"name": "gitlab-test-group", "path": "gitlab-test-group"}) +def test_group_saml_group_links(group): group.saml_group_links.create( {"saml_group_name": "saml-group-1", "access_level": 10} )