diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts index 79dd79dffa..28c5eb2775 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts @@ -398,12 +398,12 @@ describe('GitLabClient', () => { ]); }); - describe('listSaasUsers', () => { + describe('get gitlab.com users', () => { it('gets all users under group', async () => { server.use( graphql .link(`${MOCK_CONFIG.baseUrl}/api/graphql`) - .query('listSaasUsers', async (_, res, ctx) => + .query('getGroupMembers', async (_, res, ctx) => res( ctx.data({ group: { @@ -436,7 +436,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const saasMembers = (await client.listSaasUsers('group1')).items; + const saasMembers = ( + await client.getGroupMembers('group1', 'DIRECT, DESCENDANTS') + ).items; const expectedSaasMember = [ { id: 1, @@ -457,7 +459,7 @@ describe('GitLabClient', () => { server.use( graphql .link(`${MOCK_CONFIG.baseUrl}/api/graphql`) - .query('listSaasUsers', async (_, res, ctx) => + .query('getGroupMembers', async (_, res, ctx) => res( ctx.data({ group: {}, @@ -470,7 +472,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const saasMembers = (await client.listSaasUsers('group1')).items; + const saasMembers = ( + await client.getGroupMembers('group1', 'DIRECT, DESCENDANTS') + ).items; expect(saasMembers).toEqual([]); }); @@ -479,7 +483,7 @@ describe('GitLabClient', () => { server.use( graphql .link(`${MOCK_CONFIG.baseUrl}/api/graphql`) - .query('listSaasUsers', async (_, res, ctx) => + .query('getGroupMembers', async (_, res, ctx) => res( ctx.errors([ { message: 'Unexpected end of document', locations: [] }, @@ -492,7 +496,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - await expect(() => client.listSaasUsers('group1')).rejects.toThrow( + await expect(() => + client.getGroupMembers('group1', 'DIRECT, DESCENDANTS'), + ).rejects.toThrow( 'GraphQL errors: [{"message":"Unexpected end of document","locations":[]}]', ); }); @@ -500,7 +506,7 @@ describe('GitLabClient', () => { server.use( graphql .link(`${MOCK_CONFIG.baseUrl}/api/graphql`) - .query('listSaasUsers', async (req, res, ctx) => + .query('getGroupMembers', async (req, res, ctx) => res( ctx.data({ group: { @@ -547,7 +553,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const saasMembers = (await client.listSaasUsers('group1')).items; + const saasMembers = ( + await client.getGroupMembers('group1', 'DIRECT, DESCENDANTS') + ).items; const expectedSaasMember1 = { id: 1, @@ -751,7 +759,19 @@ describe('GitLabClient', () => { ctx.data({ group: { groupMembers: { - nodes: [{ user: { id: 'gid://gitlab/User/1' } }], + nodes: [ + { + user: { + id: 'gid://gitlab/User/1', + username: 'user1', + publicEmail: 'user1@example.com', + name: 'user1', + state: 'active', + webUrl: 'user1.com', + avatarUrl: 'user1', + }, + }, + ], pageInfo: { endCursor: 'end', hasNextPage: false, @@ -767,9 +787,19 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const members = await client.getGroupMembers('group1'); + const members = await client.getGroupMembers('group1', 'DIRECT'); - expect(members).toEqual([1]); + const user = { + id: 1, + username: 'user1', + email: 'user1@example.com', + name: 'user1', + state: 'active', + web_url: 'user1.com', + avatar_url: 'user1', + }; + + expect(members.items).toEqual([user]); }); it('gets member IDs with token without full permissions', async () => { @@ -789,9 +819,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const members = await client.getGroupMembers('group1'); + const members = await client.getGroupMembers('group1', 'DIRECT'); - expect(members).toEqual([]); + expect(members.items).toEqual([]); }); it('rejects when GraphQL returns errors', async () => { @@ -811,7 +841,9 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - await expect(() => client.getGroupMembers('group1')).rejects.toThrow( + await expect(() => + client.getGroupMembers('group1', 'DIRECT'), + ).rejects.toThrow( 'GraphQL errors: [{"message":"Unexpected end of document","locations":[]}]', ); }); @@ -843,9 +875,10 @@ describe('GitLabClient', () => { logger: getVoidLogger(), }); - const members = await client.getGroupMembers('group1'); + const members = await client.getGroupMembers('group1', 'DIRECT'); - expect(members).toEqual([1, 2]); + expect(members.items[0].id).toEqual(1); + expect(members.items[1].id).toEqual(2); }); }); }); diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index dee9f4ec51..5eee68cbe2 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -96,79 +96,6 @@ export class GitLabClient { return this.pagedRequest(`/groups`, options); } - async listSaasUsers(groupPath: string): Promise> { - const items: GitLabUser[] = []; - let hasNextPage: boolean = false; - let endCursor: string | null = null; - - do { - const response: GitLabGroupMembersResponse = await fetch( - `${this.config.baseUrl}/api/graphql`, - { - method: 'POST', - headers: { - ...getGitLabRequestOptions(this.config).headers, - ['Content-Type']: 'application/json', - }, - body: JSON.stringify({ - variables: { group: groupPath, endCursor }, - query: `query listSaasUsers($group: ID!, $endCursor: String) { - group(fullPath: $group) { - groupMembers(first: 100, relations: [DESCENDANTS], after: $endCursor) { - nodes { - user { - id - username - publicEmail - name - state - webUrl - avatarUrl - } - } - pageInfo { - endCursor - hasNextPage - } - } - } - }`, - }), - }, - ).then(r => r.json()); - if (response.errors) { - throw new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`); - } - - if (!response.data.group?.groupMembers?.nodes) { - this.logger.warn( - `Couldn't get members under ${groupPath}. The provided token might not have sufficient permissions`, - ); - continue; - } - - const usersData = response.data.group.groupMembers.nodes; - - for (let i = 0; i < usersData.length; i++) { - const userItem = usersData[i]; - - const formattedUserResponse = { - id: Number(userItem.user.id.replace(/^gid:\/\/gitlab\/User\//, '')), - username: userItem.user.username, - email: userItem.user.publicEmail, - name: userItem.user.name, - state: userItem.user.state, - web_url: userItem.user.webUrl, - avatar_url: userItem.user.avatarUrl, - }; - - items.push(formattedUserResponse); - } - ({ hasNextPage, endCursor } = response.data.group.groupMembers.pageInfo); - } while (hasNextPage); - return { items }; - } - async listDescendantGroups( groupPath: string, ): Promise> { @@ -243,8 +170,11 @@ export class GitLabClient { return { items }; } - async getGroupMembers(groupPath: string): Promise { - const memberIds = []; + async getGroupMembers( + groupPath: string, + relations: string, + ): Promise> { + const items: GitLabUser[] = []; let hasNextPage: boolean = false; let endCursor: string | null = null; do { @@ -257,13 +187,19 @@ export class GitLabClient { ['Content-Type']: 'application/json', }, body: JSON.stringify({ - variables: { group: groupPath, endCursor }, + variables: { group: groupPath, relations: relations, endCursor }, query: `query getGroupMembers($group: ID!, $endCursor: String) { group(fullPath: $group) { - groupMembers(first: 100, relations: [DIRECT], after: $endCursor) { + groupMembers(first: 100, relations: [$relations], after: $endCursor) { nodes { user { id + username + publicEmail + name + state + webUrl + avatarUrl } } pageInfo { @@ -287,16 +223,26 @@ export class GitLabClient { continue; } - memberIds.push( - ...response.data.group.groupMembers.nodes - .filter(n => n.user) - .map(node => - Number(node.user.id.replace(/^gid:\/\/gitlab\/User\//, '')), - ), - ); + const usersData = response.data.group.groupMembers.nodes; + + for (let i = 0; i < usersData.length; i++) { + const userItem = usersData[i]; + + const formattedUserResponse = { + id: Number(userItem.user.id.replace(/^gid:\/\/gitlab\/User\//, '')), + username: userItem.user.username, + email: userItem.user.publicEmail, + name: userItem.user.name, + state: userItem.user.state, + web_url: userItem.user.webUrl, + avatar_url: userItem.user.avatarUrl, + }; + + items.push(formattedUserResponse); + } ({ hasNextPage, endCursor } = response.data.group.groupMembers.pageInfo); } while (hasNextPage); - return memberIds; + return { items }; } /** diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index 0a1284b22c..1b1357c1bf 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -550,7 +550,7 @@ describe('GitlabOrgDiscoveryEntityProvider', () => { ), graphql .link('https://gitlab.com/api/graphql') - .query('listSaasUsers', async (_, res, ctx) => + .query('getGroupMembers', async (_, res, ctx) => res( ctx.data({ group: { @@ -634,7 +634,7 @@ describe('GitlabOrgDiscoveryEntityProvider', () => { name: 'testuser1', }, spec: { - memberOf: ['group2'], + memberOf: ['group2', 'group3'], profile: { displayName: 'Test User 1', email: 'testuser1@example.com', @@ -659,7 +659,7 @@ describe('GitlabOrgDiscoveryEntityProvider', () => { name: 'testuser2', }, spec: { - memberOf: ['group3'], + memberOf: ['group2', 'group3'], profile: { displayName: 'Test User 2', email: 'testuser2@example.com', diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index a14a00d718..bec3b06661 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -190,8 +190,12 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { }); } else { groups = (await client.listDescendantGroups(this.config.group)).items; - users = (await client.listSaasUsers(this.config.group.split('/')[0])) - .items; + users = ( + await client.getGroupMembers( + this.config.group.split('/')[0], + 'DIRECT, DESCENDANTS', + ) + ).items; } const idMappedUser: { [userId: number]: GitLabUser } = {}; @@ -236,8 +240,13 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { groupRes.scanned++; groupRes.matches.push(group); - for (const id of await client.getGroupMembers(group.full_path)) { - const user = idMappedUser[id]; + const groupUsers = await client.getGroupMembers( + group.full_path, + 'DIRECT', + ); + + for (const groupUser of groupUsers.items) { + const user = idMappedUser[groupUser.id]; if (user) { user.groups = (user.groups ?? []).concat(group); }