Skip to content

Add groups field for User type in GraphQL API

Mario Celi requested to merge 26732-manageable-groups-api into master

What does this MR do?

Adds groups to the UserType in GraphQL.

Database plans

Group Memberships max access level

RAW SQL
SELECT
   MAX("members"."access_level") AS maximum_access_level,
   "members"."source_id" AS members_source_id 
FROM
   "members" 
   LEFT OUTER JOIN
      "users" 
      ON "members"."user_id" = "users"."id" 
WHERE
   "members"."type" = 'GroupMember' 
   AND "members"."source_type" = 'Namespace' 
   AND "users"."state" = 'active' 
   AND "members"."requested_at" IS NULL 
   AND "members"."invite_token" IS NULL 
   AND 
   (
      members.access_level > 5
   )
   AND 
   (
      members.access_level > 5
   )
   AND "members"."user_id" = 8110537 
   AND "members"."source_id" IN 
   (
      6543,
      9970,
      9126439,
      12292726,
      12468552,
      12705441,
      12772697,
      12850394,
      12850410
   )
GROUP BY
   "members"."source_id"

All groups where user can create projects

RAW SQL
SELECT
   "namespaces".* 
FROM
   (
(WITH RECURSIVE "base_and_descendants" AS 
      (
(
         SELECT
            "namespaces".* 
         FROM
            "namespaces" 
            INNER JOIN
               "members" 
               ON "namespaces"."id" = "members"."source_id" 
         WHERE
            "members"."type" = 'GroupMember' 
            AND "members"."source_type" = 'Namespace' 
            AND "namespaces"."type" = 'Group' 
            AND "members"."user_id" = 8110537 
            AND "members"."requested_at" IS NULL 
            AND 
            (
               access_level >= 10
            )
            AND "members"."access_level" IN 
            (
               40,
               50
            )
) 
         UNION
(
         SELECT
            "namespaces".* 
         FROM
            "namespaces", "base_and_descendants" 
         WHERE
            "namespaces"."type" = 'Group' 
            AND "namespaces"."parent_id" = "base_and_descendants"."id")
      )
      SELECT
         "namespaces".* 
      FROM
         "base_and_descendants" AS "namespaces") 
      UNION
(
      SELECT
         "namespaces".* 
      FROM
         "namespaces" 
      WHERE
         "namespaces"."type" = 'Group' 
         AND "namespaces"."id" IN 
         (
            WITH RECURSIVE "base_and_descendants" AS 
            (
(
               SELECT
                  "namespaces".* 
               FROM
                  "namespaces" 
                  INNER JOIN
                     "members" 
                     ON "namespaces"."id" = "members"."source_id" 
               WHERE
                  "members"."type" = 'GroupMember' 
                  AND "members"."source_type" = 'Namespace' 
                  AND "namespaces"."type" = 'Group' 
                  AND "members"."user_id" = 8110537 
                  AND "members"."requested_at" IS NULL 
                  AND 
                  (
                     access_level >= 10
                  )
                  AND "members"."access_level" = 30) 
               UNION
(
               SELECT
                  "namespaces".* 
               FROM
                  "namespaces", "base_and_descendants" 
               WHERE
                  "namespaces"."type" = 'Group' 
                  AND "namespaces"."parent_id" = "base_and_descendants"."id")
            )
            SELECT
               "id" 
            FROM
               "base_and_descendants" AS "namespaces"
         )
         AND 
         (
            "namespaces"."project_creation_level" = 2 
            OR "namespaces"."project_creation_level" IS NULL
         )
)
   )
   namespaces 
WHERE
   "namespaces"."type" = 'Group' 
ORDER BY
   "namespaces"."path" ASC,
   "namespaces"."id" ASC

All groups where a user can create a project and searched by path or name

RAW SQL
SELECT
   "namespaces".* 
FROM
   (
(WITH RECURSIVE "base_and_descendants" AS 
      (
(
         SELECT
            "namespaces".* 
         FROM
            "namespaces" 
            INNER JOIN
               "members" 
               ON "namespaces"."id" = "members"."source_id" 
         WHERE
            "members"."type" = 'GroupMember' 
            AND "members"."source_type" = 'Namespace' 
            AND "namespaces"."type" = 'Group' 
            AND "members"."user_id" = 8110537 
            AND "members"."requested_at" IS NULL 
            AND 
            (
               access_level >= 10
            )
            AND "members"."access_level" IN 
            (
               40,
               50
            )
) 
         UNION
(
         SELECT
            "namespaces".* 
         FROM
            "namespaces", "base_and_descendants" 
         WHERE
            "namespaces"."type" = 'Group' 
            AND "namespaces"."parent_id" = "base_and_descendants"."id")
      )
      SELECT
         "namespaces".* 
      FROM
         "base_and_descendants" AS "namespaces") 
      UNION
(
      SELECT
         "namespaces".* 
      FROM
         "namespaces" 
      WHERE
         "namespaces"."type" = 'Group' 
         AND "namespaces"."id" IN 
         (
            WITH RECURSIVE "base_and_descendants" AS 
            (
(
               SELECT
                  "namespaces".* 
               FROM
                  "namespaces" 
                  INNER JOIN
                     "members" 
                     ON "namespaces"."id" = "members"."source_id" 
               WHERE
                  "members"."type" = 'GroupMember' 
                  AND "members"."source_type" = 'Namespace' 
                  AND "namespaces"."type" = 'Group' 
                  AND "members"."user_id" = 8110537 
                  AND "members"."requested_at" IS NULL 
                  AND 
                  (
                     access_level >= 10
                  )
                  AND "members"."access_level" = 30) 
               UNION
(
               SELECT
                  "namespaces".* 
               FROM
                  "namespaces", "base_and_descendants" 
               WHERE
                  "namespaces"."type" = 'Group' 
                  AND "namespaces"."parent_id" = "base_and_descendants"."id")
            )
            SELECT
               "id" 
            FROM
               "base_and_descendants" AS "namespaces"
         )
         AND 
         (
            "namespaces"."project_creation_level" = 2 
            OR "namespaces"."project_creation_level" IS NULL
         )
)
   )
   namespaces 
WHERE
   "namespaces"."type" = 'Group' 
   AND 
   (
      "namespaces"."path" ILIKE '%gitlab%' 
      OR "namespaces"."name" ILIKE '%gitlab%'
   )
ORDER BY
   "namespaces"."path" ASC,
   "namespaces"."id" ASC LIMIT 100

Sample GraphQL Queries

Fetch groups where current user can create projects

Sample Query
query groupsForProjectCreation{
  currentUser {
    groups(search: "git", permission: CREATE_PROJECT) {
      nodes {
        path
        name
        id
      }
    }
  }
}
Sample Response
{
  "data": {
    "currentUser": {
      "groups": {
        "nodes": [
          {
            "path": "gitlab-instance-ff9ae9cb",
            "name": "GitLab Instance",
            "id": "gid://gitlab/Group/52"
          },
          {
            "path": "gitlab-org",
            "name": "Gitlab Org",
            "id": "gid://gitlab/Group/22"
          }
        ]
      }
    }
  }
}

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

Security

Does this MR contain changes to processing or storing of credentials or tokens, authorization and authentication methods or other items described in the security review guidelines? If not, then delete this Security section.

  • Label as security and @ mention @gitlab-com/gl-security/appsec
  • The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
  • Security reports checked/validated by a reviewer from the AppSec team

Related to #26732 (closed)

Edited by Mario Celi

Merge request reports