From 1b3763eaa511b2f3403e15a5717131a3f2cbc99a Mon Sep 17 00:00:00 2001
From: Simon Weimann <simon.weimann@gmail.com>
Date: Fri, 20 Oct 2023 10:09:47 +0200
Subject: [PATCH] Add SAML group sync to GroupApi (#1038)

---
 src/main/java/org/gitlab4j/api/GroupApi.java  | 75 ++++++++++++++++++-
 .../gitlab4j/api/models/SamlGroupLink.java    | 32 ++++++++
 .../org/gitlab4j/api/TestGitLabApiBeans.java  |  7 ++
 .../org/gitlab4j/api/saml-group-link.json     |  4 +
 4 files changed, 115 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/org/gitlab4j/api/models/SamlGroupLink.java
 create mode 100644 src/test/resources/org/gitlab4j/api/saml-group-link.json

diff --git a/src/main/java/org/gitlab4j/api/GroupApi.java b/src/main/java/org/gitlab4j/api/GroupApi.java
index 559894a8..6c106bab 100644
--- a/src/main/java/org/gitlab4j/api/GroupApi.java
+++ b/src/main/java/org/gitlab4j/api/GroupApi.java
@@ -28,6 +28,7 @@ import org.gitlab4j.api.models.IterationFilter;
 import org.gitlab4j.api.models.LdapGroupLink;
 import org.gitlab4j.api.models.Member;
 import org.gitlab4j.api.models.Project;
+import org.gitlab4j.api.models.SamlGroupLink;
 import org.gitlab4j.api.models.Variable;
 import org.gitlab4j.api.models.Visibility;
 import org.gitlab4j.api.utils.ISO8601;
@@ -1183,9 +1184,9 @@ public class GroupApi extends AbstractApi {
 
     /**
      * Get the list of LDAP group links.
-     * 
+     *
      * <pre><code>GitLab Endpoint: GET /groups/:id/ldap_group_links</code></pre>
-     * 
+     *
      * @param groupIdOrPath the group ID, path of the group, or a Group instance holding the group ID or path
      * @return a list of LDAP group links
      * @throws GitLabApiException if any exception occurs
@@ -1275,6 +1276,74 @@ public class GroupApi extends AbstractApi {
         delete(Response.Status.OK, null, "groups", getGroupIdOrPath(groupIdOrPath), "ldap_group_links", provider, cn);
     }
 
+    /**
+     * Get the list of SAML group links.
+     *
+     * <pre><code>GitLab Endpoint: GET /groups/:id/saml_group_links</code></pre>
+     *
+     * @param groupIdOrPath the group ID, path of the group, or a Group instance holding the group ID or path
+     * @return a list of SAML group links
+     * @throws GitLabApiException if any exception occurs
+     */
+    public List<SamlGroupLink> getSamlGroupLinks(Object groupIdOrPath) throws GitLabApiException {
+        Response response = get(Response.Status.OK, null, "groups", getGroupIdOrPath(groupIdOrPath), "saml_group_links");
+        return (response.readEntity(new GenericType<List<SamlGroupLink>>() {}));
+    }
+
+    /**
+     * Adds an SAML group link.
+     *
+     * <pre><code>GitLab Endpoint: POST /groups/:id/saml_group_links</code></pre>
+     *
+     * @param groupIdOrPath the group ID, path of the group, or a Group instance holding the group ID or path
+     * @param samlGroupName the name of the SAML group
+     * @param groupAccess the minimum access level for members of the SAML group
+     * @throws GitLabApiException if any exception occurs
+     */
+    public void addSamlGroupLink(Object groupIdOrPath, String samlGroupName, AccessLevel groupAccess) throws GitLabApiException {
+
+        if (groupAccess == null) {
+            throw new RuntimeException("groupAccess cannot be null or empty");
+        }
+
+        addSamlGroupLink(groupIdOrPath, samlGroupName, groupAccess.toValue());
+    }
+
+    /**
+     * Adds an SAML group link.
+     *
+     * <pre><code>GitLab Endpoint: POST /groups/:id/saml_group_links</code></pre>
+     *
+     * @param groupIdOrPath the group ID, path of the group, or a Group instance holding the group ID or path
+     * @param samlGroupName the name of the SAML group
+     * @param groupAccess the minimum access level for members of the SAML group
+     * @throws GitLabApiException if any exception occurs
+     */
+    public void addSamlGroupLink(Object groupIdOrPath, String samlGroupName, Integer groupAccess) throws GitLabApiException {
+        GitLabApiForm formData = new GitLabApiForm()
+            .withParam("saml_group_name",  samlGroupName, true)
+            .withParam("access_level", groupAccess, true);
+        post(Response.Status.CREATED, formData, "groups", getGroupIdOrPath(groupIdOrPath), "saml_group_links");
+    }
+
+    /**
+     * Deletes an SAML group link.
+     *
+     * <pre><code>GitLab Endpoint: DELETE /groups/:id/saml_group_links/:cn</code></pre>
+     *
+     * @param groupIdOrPath the group ID, path of the group, or a Group instance holding the group ID or path
+     * @param samlGroupName the name of the SAML group to delete
+     * @throws GitLabApiException if any exception occurs
+     */
+    public void deleteSamlGroupLink(Object groupIdOrPath, String samlGroupName) throws GitLabApiException {
+
+        if (samlGroupName == null || samlGroupName.trim().isEmpty()) {
+            throw new RuntimeException("samlGroupName cannot be null or empty");
+        }
+
+        delete(Response.Status.OK, null, "groups", getGroupIdOrPath(groupIdOrPath), "saml_group_links", samlGroupName);
+    }
+
     /**
      * Get list of a group鈥檚 variables.
      *
@@ -1961,7 +2030,7 @@ public class GroupApi extends AbstractApi {
 
         delete(Response.Status.OK, null, "groups", getGroupIdOrPath(groupIdOrPath), "custom_attributes", key);
     }
-    
+
     /**
      * Lists group iterations.
      *
diff --git a/src/main/java/org/gitlab4j/api/models/SamlGroupLink.java b/src/main/java/org/gitlab4j/api/models/SamlGroupLink.java
new file mode 100644
index 00000000..2d85058d
--- /dev/null
+++ b/src/main/java/org/gitlab4j/api/models/SamlGroupLink.java
@@ -0,0 +1,32 @@
+
+package org.gitlab4j.api.models;
+
+import org.gitlab4j.api.utils.JacksonJson;
+
+public class SamlGroupLink {
+
+    private String name;
+
+    private AccessLevel accessLevel;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String aName) {
+        this.name = aName;
+    }
+
+    public AccessLevel getAccessLevel() {
+       return accessLevel;
+    }
+
+    public void setAccessLevel(AccessLevel aAccessLevel) {
+        accessLevel = aAccessLevel;
+    }
+
+    @Override
+    public String toString() {
+        return (JacksonJson.toJsonString(this));
+    }
+}
diff --git a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java
index fcffc9de..d5fb0e61 100644
--- a/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java
+++ b/src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java
@@ -117,6 +117,7 @@ import org.gitlab4j.api.models.RemoteMirror;
 import org.gitlab4j.api.models.RepositoryFile;
 import org.gitlab4j.api.models.Runner;
 import org.gitlab4j.api.models.RunnerDetail;
+import org.gitlab4j.api.models.SamlGroupLink;
 import org.gitlab4j.api.models.SearchBlob;
 import org.gitlab4j.api.models.Snippet;
 import org.gitlab4j.api.models.SshKey;
@@ -808,6 +809,12 @@ public class TestGitLabApiBeans {
         assertTrue(compareJson(link, "ldap-group-link.json"));
     }
 
+    @Test
+    public void testSamlGroupLink() throws Exception {
+        SamlGroupLink link = unmarshalResource(SamlGroupLink.class, "saml-group-link.json");
+        assertTrue(compareJson(link, "saml-group-link.json"));
+    }
+
     @Test
     public void testSearchBlobs() throws Exception {
         List<SearchBlob> searchResults = unmarshalResourceList(SearchBlob.class, "wiki-blobs.json");
diff --git a/src/test/resources/org/gitlab4j/api/saml-group-link.json b/src/test/resources/org/gitlab4j/api/saml-group-link.json
new file mode 100644
index 00000000..38dce087
--- /dev/null
+++ b/src/test/resources/org/gitlab4j/api/saml-group-link.json
@@ -0,0 +1,4 @@
+{
+    "access_level": 30,
+    "name": "A_GITLAB_DEVELOPER"
+}
-- 
GitLab