Merge pull request #24936 from hghtwr/master
#24889 - Feature: Fetch Gitlab users for self-hosted Gitlab based on group assignment
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
The Gitlab configuration supports an additional optional boolean key `catalog.providers.gitlab.<your-org>.restrictUsersToGroup`. Setting this to `true` will make Backstage only import users from the group defined in the `group` key, instead of all users in the organisation (self-hosted) or of the root group (SaaS). It will default to false, keeping the original implementation intact, when not explicitly set.
|
||||
@@ -94,6 +94,7 @@ typings/
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.envrc
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
@@ -196,11 +196,24 @@ which contain members will be ingested.
|
||||
|
||||
### Users
|
||||
|
||||
For self hosted, all `User` entities are ingested from the entire instance.
|
||||
For self hosted, all `User` entities are ingested from the entire instance by default.
|
||||
|
||||
For gitlab.com `User` entities for users who have [direct or inherited membership](https://docs.gitlab.com/ee/user/project/members/index.html#membership-types)
|
||||
of the top-level group for the configured group path will be ingested.
|
||||
|
||||
In both cases (SaaS & self hosted), you can limit the ingested users to users directly assigned to the group defined in your `app-config.yaml` by setting the configuration key `restrictUsersToGroup: true`. This is especially useful when you have a large user base that you don't want to import by default.
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
providers:
|
||||
gitlab:
|
||||
yourProviderId:
|
||||
host: gitlab.com ## Could also be self hosted.
|
||||
orgEnabled: true
|
||||
group: org/teams # Required for gitlab.com when `orgEnabled: true`. Optional for self managed. Must not end with slash. Accepts only groups under the provided path (which will be stripped)
|
||||
restrictUsersToGroup: true # Backstage will ingest only users directly assigned to org/teams.
|
||||
```
|
||||
|
||||
### Limiting `User` and `Group` entity ingestion in the provider
|
||||
|
||||
Optionally, you can limit the entity types ingested by the provider when using
|
||||
|
||||
@@ -21,7 +21,6 @@ const backend = createBackend();
|
||||
backend.add(import('@backstage/plugin-auth-backend'));
|
||||
backend.add(import('./authModuleGithubProvider'));
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
|
||||
|
||||
backend.add(import('@backstage/plugin-app-backend/alpha'));
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
|
||||
backend.add(
|
||||
|
||||
@@ -98,6 +98,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
export type GitlabProviderConfig = {
|
||||
host: string;
|
||||
group: string;
|
||||
restrictUsersToGroup?: boolean;
|
||||
id: string;
|
||||
branch?: string;
|
||||
fallbackBranch: string;
|
||||
@@ -122,6 +123,7 @@ export type GitLabUser = {
|
||||
avatar_url: string;
|
||||
groups?: GitLabGroup[];
|
||||
group_saml_identity?: GitLabGroupSamlIdentity;
|
||||
is_using_seat?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
all_groups_response,
|
||||
all_projects_response,
|
||||
all_saas_users_response,
|
||||
all_self_hosted_group1_members,
|
||||
subgroup_saas_users_response,
|
||||
all_users_response,
|
||||
apiBaseUrl,
|
||||
apiBaseUrlSaas,
|
||||
@@ -63,11 +65,21 @@ const httpHandlers = [
|
||||
rest.get(`${apiBaseUrl}/groups/42`, (_, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
|
||||
}),
|
||||
rest.get(`${apiBaseUrl}/groups/group1/members/all`, (_req, res, ctx) => {
|
||||
return res(ctx.json(all_self_hosted_group1_members));
|
||||
}),
|
||||
|
||||
rest.get(`${apiBaseUrlSaas}/groups/group1/members/all`, (_req, res, ctx) => {
|
||||
return res(ctx.json(all_saas_users_response));
|
||||
}),
|
||||
|
||||
rest.get(
|
||||
`${apiBaseUrlSaas}/groups/subgroup1/members/all`,
|
||||
(_req, res, ctx) => {
|
||||
return res(ctx.json(subgroup_saas_users_response)); // To-DO change
|
||||
},
|
||||
),
|
||||
|
||||
/**
|
||||
* Users REST endpoint mocks
|
||||
*/
|
||||
@@ -186,7 +198,34 @@ const graphqlHandlers = [
|
||||
.link(graphQlBaseUrl)
|
||||
.query('getGroupMembers', async (req, res, ctx) => {
|
||||
const { group, relations } = req.variables;
|
||||
|
||||
// group is actually full_path
|
||||
if (group === 'group1/subgroup1' && relations.includes('DIRECT')) {
|
||||
return res(
|
||||
ctx.data({
|
||||
group: {
|
||||
groupMembers: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (group === 'group1' && relations.includes('DIRECT')) {
|
||||
return res(
|
||||
ctx.data({
|
||||
@@ -367,7 +406,31 @@ const graphqlHandlers = [
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
if (group === 'group1') {
|
||||
return res(
|
||||
ctx.data({
|
||||
group: {
|
||||
descendantGroups: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'gid://gitlab/Group/6',
|
||||
name: 'subgroup1',
|
||||
description: 'description1',
|
||||
fullPath: 'group1/subgroup1',
|
||||
parent: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: 'end',
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (group === 'group-with-parent') {
|
||||
return res(
|
||||
ctx.data({
|
||||
|
||||
@@ -596,6 +596,127 @@ export const config_org_double_integration: MockObject = {
|
||||
},
|
||||
};
|
||||
|
||||
export const config_org_group_saas = {
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'https://gitlab.com/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'gitlab.com',
|
||||
group: 'group1',
|
||||
orgEnabled: true,
|
||||
skipForkedRepos: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const config_org_group_restrictUsers_false_saas = {
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'https://gitlab.com/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'gitlab.com',
|
||||
group: 'group1',
|
||||
orgEnabled: true,
|
||||
skipForkedRepos: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const config_org_group_restrictUsers_true_saas = {
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'https://gitlab.com/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'gitlab.com',
|
||||
group: 'group1/subgroup1',
|
||||
restrictUsersToGroup: true,
|
||||
orgEnabled: true,
|
||||
skipForkedRepos: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const config_org_group_selfHosted = {
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'example.com',
|
||||
apiBaseUrl: 'https://example.com/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'example.com',
|
||||
group: 'group1',
|
||||
orgEnabled: true,
|
||||
skipForkedRepos: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const config_org_group_restrictUsers_true_selfHosted = {
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'example.com',
|
||||
apiBaseUrl: 'https://example.com/api/v4',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'example.com',
|
||||
group: 'group1',
|
||||
orgEnabled: true,
|
||||
skipForkedRepos: true,
|
||||
restrictUsersToGroup: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
/**
|
||||
* GitLab API responses
|
||||
*/
|
||||
@@ -822,6 +943,12 @@ export const all_groups_response: GitLabGroup[] = [
|
||||
description: '',
|
||||
full_path: 'parent1/nonMatchingGroup',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'subgroup1',
|
||||
description: '',
|
||||
full_path: 'group1/subgroup1',
|
||||
},
|
||||
];
|
||||
|
||||
export const group_with_parent: MockObject[] = [
|
||||
@@ -1708,7 +1835,7 @@ export const expected_full_org_scan_entities: MockObject[] = [
|
||||
name: 'JohnDoe',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['group1'],
|
||||
memberOf: ['group1', 'group1-subgroup1'],
|
||||
profile: {
|
||||
displayName: 'John Doe',
|
||||
email: 'john.doe@company.com',
|
||||
@@ -1815,6 +1942,30 @@ export const expected_full_org_scan_entities: MockObject[] = [
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'example.com/team-path': 'group1/subgroup1',
|
||||
},
|
||||
name: 'group1-subgroup1',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
profile: {
|
||||
displayName: 'subgroup1',
|
||||
},
|
||||
type: 'team',
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
];
|
||||
|
||||
export const expected_full_org_scan_entities_saas: MockObject[] = [
|
||||
@@ -1871,3 +2022,269 @@ export const expected_full_org_scan_entities_saas: MockObject[] = [
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
];
|
||||
|
||||
export const subgroup_saas_users_response: MockObject[] = [
|
||||
{
|
||||
access_level: 30,
|
||||
created_at: '2023-07-17T08:58:34.984Z',
|
||||
expires_at: null,
|
||||
id: 12,
|
||||
username: 'testuser1',
|
||||
name: 'Test User 1',
|
||||
state: 'active',
|
||||
avatar_url: 'https://secure.gravatar.com/',
|
||||
web_url: 'https://gitlab.com/testuser1',
|
||||
email: 'testuser1@example.com',
|
||||
group_saml_identity: {
|
||||
provider: 'group_saml',
|
||||
extern_uid: '51',
|
||||
saml_provider_id: 1,
|
||||
},
|
||||
is_using_seat: true,
|
||||
membership_state: 'active',
|
||||
},
|
||||
{
|
||||
access_level: 50,
|
||||
created_at: '2023-07-15T08:58:34.984Z',
|
||||
expires_at: '2023-10-26',
|
||||
id: 54,
|
||||
username: 'group_100_bot_23dc8057bef66e05181f39be4652577c',
|
||||
name: 'Token Bot',
|
||||
state: 'active',
|
||||
avatar_url: 'https://secure.gravatar.com/',
|
||||
web_url:
|
||||
'https://gitlab.com/group_100_bot_23dc8057bef66e05181f39be4652577c',
|
||||
group_saml_identity: null,
|
||||
is_using_seat: false,
|
||||
membership_state: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
export const expected_subgroup_org_scan_entities_saas: MockObject[] = [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://gitlab.com/testuser1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://gitlab.com/testuser1',
|
||||
'gitlab.com/user-login': 'https://gitlab.com/testuser1',
|
||||
'gitlab.com/saml-external-uid': '51',
|
||||
},
|
||||
name: 'testuser1',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
profile: {
|
||||
displayName: 'Test User 1',
|
||||
email: 'testuser1@example.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
];
|
||||
|
||||
// Simulate return of all users but only with membership of the descendants of config.group
|
||||
export const expected_full_members_group_org_scan_entities: MockObject[] = [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': 'url:https://example.com/JohnDoe',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/JohnDoe',
|
||||
'example.com/user-login': 'https://gitlab.example/john_doe',
|
||||
},
|
||||
name: 'JohnDoe',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['subgroup1'],
|
||||
profile: {
|
||||
displayName: 'John Doe',
|
||||
email: 'john.doe@company.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': 'url:https://example.com/JaneDoe',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/JaneDoe',
|
||||
'example.com/user-login': 'https://gitlab.example/jane_doe',
|
||||
},
|
||||
name: 'JaneDoe',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
profile: {
|
||||
displayName: 'Jane Doe',
|
||||
email: 'jane.doe@company.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://example.com/MarySmith',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/MarySmith',
|
||||
'example.com/user-login': 'https://gitlab.example/mary_smith',
|
||||
},
|
||||
name: 'MarySmith',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
profile: {
|
||||
displayName: 'Mary Smith',
|
||||
email: 'mary.smith@company.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://example.com/MarioMario',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/MarioMario',
|
||||
'example.com/user-login': 'https://gitlab.example/mario_mario',
|
||||
},
|
||||
name: 'MarioMario',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
profile: {
|
||||
displayName: 'Mario Mario',
|
||||
email: 'mario.mario-company.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'example.com/team-path': 'group1/subgroup1',
|
||||
},
|
||||
name: 'subgroup1',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
profile: {
|
||||
displayName: 'subgroup1',
|
||||
},
|
||||
type: 'team',
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
];
|
||||
|
||||
export const expected_group_members_group_org_scan_entities: MockObject[] = [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': 'url:https://example.com/JohnDoe',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/JohnDoe',
|
||||
'example.com/user-login': 'https://gitlab.example/john_doe',
|
||||
},
|
||||
name: 'JohnDoe',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['subgroup1'],
|
||||
profile: {
|
||||
displayName: 'John Doe',
|
||||
email: 'john.doe@company.com',
|
||||
picture: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://example.com/group1/subgroup1',
|
||||
'example.com/team-path': 'group1/subgroup1',
|
||||
},
|
||||
name: 'subgroup1',
|
||||
description: 'description1',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
profile: {
|
||||
displayName: 'subgroup1',
|
||||
},
|
||||
type: 'team',
|
||||
},
|
||||
},
|
||||
locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
},
|
||||
];
|
||||
|
||||
export const all_self_hosted_group1_members: MockObject[] = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'JohnDoe',
|
||||
name: 'John Doe',
|
||||
state: 'active',
|
||||
email: 'john.doe@company.com',
|
||||
avatar_url: 'https://secure.gravatar.com/',
|
||||
web_url: 'https://gitlab.example/john_doe',
|
||||
},
|
||||
// inactive
|
||||
{
|
||||
id: 5,
|
||||
username: 'MarioMario',
|
||||
name: 'Mario Mario',
|
||||
state: 'inactive',
|
||||
email: 'mario.mario-company.com',
|
||||
avatar_url: 'https://secure.gravatar.com/',
|
||||
web_url: 'https://gitlab.example/mario_mario',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -114,6 +114,16 @@ export class GitLabClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
async listGroupMembers(
|
||||
groupPath: string,
|
||||
options?: CommonListOptions,
|
||||
): Promise<PagedResponse<GitLabUser>> {
|
||||
return this.pagedRequest(
|
||||
`/groups/${encodeURIComponent(groupPath)}/members/all`,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async listUsers(
|
||||
options?: UserListOptions,
|
||||
): Promise<PagedResponse<GitLabUser>> {
|
||||
@@ -128,13 +138,10 @@ export class GitLabClient {
|
||||
groupPath: string,
|
||||
options?: CommonListOptions,
|
||||
): Promise<PagedResponse<GitLabUser>> {
|
||||
return this.pagedRequest(
|
||||
`/groups/${encodeURIComponent(groupPath)}/members/all`,
|
||||
{
|
||||
...options,
|
||||
show_seat_info: true,
|
||||
},
|
||||
).then(resp => {
|
||||
return this.listGroupMembers(groupPath, {
|
||||
...options,
|
||||
show_seat_info: true,
|
||||
}).then(resp => {
|
||||
resp.items = resp.items.filter(user => user.is_using_seat);
|
||||
return resp;
|
||||
});
|
||||
|
||||
@@ -60,6 +60,7 @@ export type GitLabUser = {
|
||||
avatar_url: string;
|
||||
groups?: GitLabGroup[];
|
||||
group_saml_identity?: GitLabGroupSamlIdentity;
|
||||
is_using_seat?: boolean; // Only available in responses from the group members endpoint
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,6 +150,12 @@ export type GitlabProviderConfig = {
|
||||
* Accepts only groups under the provided path (which will be stripped)
|
||||
*/
|
||||
group: string;
|
||||
|
||||
/**
|
||||
* If true, the provider will only ingest users that are part of the configured group.
|
||||
*/
|
||||
restrictUsersToGroup?: boolean;
|
||||
|
||||
/**
|
||||
* ???
|
||||
*/
|
||||
|
||||
+167
-4
@@ -260,15 +260,16 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => {
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
const userEntities = mock.expected_full_org_scan_entities.filter(
|
||||
const entities = mock.expected_full_org_scan_entities.filter(
|
||||
element => element.entity.metadata.name !== 'MarioMario',
|
||||
); // filter out user with non matched e-mail
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(userEntities).not.toHaveLength(mock.all_users_response.length);
|
||||
expect(entities).not.toHaveLength(
|
||||
mock.expected_full_org_scan_entities.length,
|
||||
);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: userEntities,
|
||||
entities: entities,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -301,6 +302,168 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => {
|
||||
entities: mock.expected_full_org_scan_entities_saas,
|
||||
});
|
||||
});
|
||||
|
||||
// This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas
|
||||
it('SaaS: should get all saas root group users when restrictUsersToGroup is not set', async () => {
|
||||
const config = new ConfigReader(mock.config_org_group_saas);
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: mock.expected_full_org_scan_entities_saas, //
|
||||
});
|
||||
});
|
||||
|
||||
// This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas
|
||||
it('SaaS: should get all saas root group users when restrictUsersToGroup is false', async () => {
|
||||
const config = new ConfigReader(
|
||||
mock.config_org_group_restrictUsers_false_saas,
|
||||
);
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: mock.expected_full_org_scan_entities_saas, //
|
||||
});
|
||||
});
|
||||
|
||||
// This should return only members of the SaaS subgroup (group1/subgroup1) -> expected_subgroup_org_scan_entities_saas
|
||||
it('SaaS: should get only subgroup users when restrictUsersToGroup is true', async () => {
|
||||
const config = new ConfigReader(
|
||||
mock.config_org_group_restrictUsers_true_saas,
|
||||
);
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: mock.expected_subgroup_org_scan_entities_saas,
|
||||
});
|
||||
});
|
||||
|
||||
// This should return all members of the self-hosted instance regardless of the group set -> expected_full_members_group_org_scan_entities
|
||||
// All instance members, but only the group entities below the config.group
|
||||
it('Self-hosted: should get all instance users when restrictUsersToGroup is not set', async () => {
|
||||
const config = new ConfigReader(mock.config_org_group_selfHosted);
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: mock.expected_full_members_group_org_scan_entities, // This should deliver all users but only their membership in subgroups of config.group
|
||||
});
|
||||
});
|
||||
|
||||
// This should return all members of the self-hosted config.group and all group entities of config.group -> expected_full_members_group_org_scan_entities
|
||||
it('Self-hosted: should get only groups users when restrictUsersToGroup is set', async () => {
|
||||
const config = new ConfigReader(
|
||||
mock.config_org_group_restrictUsers_true_selfHosted,
|
||||
);
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = GitlabOrgDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'GitlabOrgDiscoveryEntityProvider:test-id:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
type: 'full',
|
||||
entities: mock.expected_group_members_group_org_scan_entities, // This should deliver all users but only their membership in subgroups of config.group
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GitlabOrgDiscoveryEntityProvider with events support', () => {
|
||||
|
||||
+26
-10
@@ -356,7 +356,23 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
let groups;
|
||||
let users;
|
||||
|
||||
if (this.gitLabClient.isSelfManaged()) {
|
||||
// Self-hosted: Fetch the users either from the defined group (restrictUsersToGroup) or fetch all users from the GitLab instance
|
||||
// SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group.
|
||||
if (this.gitLabClient.isSelfManaged() && this.config.restrictUsersToGroup) {
|
||||
groups = (await this.gitLabClient.listDescendantGroups(this.config.group))
|
||||
.items;
|
||||
users = paginated<GitLabUser>(
|
||||
options =>
|
||||
this.gitLabClient.listGroupMembers(this.config.group, options), // calls /groups/<groupId>/members
|
||||
{
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
},
|
||||
);
|
||||
} else if (
|
||||
this.gitLabClient.isSelfManaged() &&
|
||||
!this.config.restrictUsersToGroup
|
||||
) {
|
||||
groups = paginated<GitLabGroup>(
|
||||
options => this.gitLabClient.listGroups(options),
|
||||
{
|
||||
@@ -365,19 +381,19 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
all_available: true,
|
||||
},
|
||||
);
|
||||
|
||||
users = paginated<GitLabUser>(
|
||||
options => this.gitLabClient.listUsers(options),
|
||||
{
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
active: true,
|
||||
},
|
||||
options => this.gitLabClient.listUsers(options), // calls /users?
|
||||
{ page: 1, per_page: 100, active: true },
|
||||
);
|
||||
} else {
|
||||
}
|
||||
// SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group.
|
||||
else {
|
||||
groups = (await this.gitLabClient.listDescendantGroups(this.config.group))
|
||||
.items;
|
||||
const rootGroup = this.config.group.split('/')[0];
|
||||
const rootGroupSplit = this.config.group.split('/');
|
||||
const rootGroup = this.config.restrictUsersToGroup
|
||||
? rootGroupSplit[rootGroupSplit.length - 1]
|
||||
: rootGroupSplit[0];
|
||||
users = paginated<GitLabUser>(
|
||||
options => this.gitLabClient.listSaaSUsers(rootGroup, options),
|
||||
{
|
||||
|
||||
@@ -60,6 +60,7 @@ describe('config', () => {
|
||||
allowInherited: false,
|
||||
schedule: undefined,
|
||||
skipForkedRepos: false,
|
||||
restrictUsersToGroup: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -98,6 +99,7 @@ describe('config', () => {
|
||||
allowInherited: false,
|
||||
schedule: undefined,
|
||||
skipForkedRepos: false,
|
||||
restrictUsersToGroup: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -136,6 +138,7 @@ describe('config', () => {
|
||||
orgEnabled: false,
|
||||
allowInherited: false,
|
||||
schedule: undefined,
|
||||
restrictUsersToGroup: false,
|
||||
skipForkedRepos: true,
|
||||
}),
|
||||
);
|
||||
@@ -177,6 +180,7 @@ describe('config', () => {
|
||||
orgEnabled: false,
|
||||
allowInherited: false,
|
||||
skipForkedRepos: false,
|
||||
restrictUsersToGroup: false,
|
||||
schedule: {
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
|
||||
@@ -51,6 +51,8 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
const schedule = config.has('schedule')
|
||||
? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))
|
||||
: undefined;
|
||||
const restrictUsersToGroup =
|
||||
config.getOptionalBoolean('restrictUsersToGroup') ?? false;
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -66,6 +68,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
orgEnabled,
|
||||
allowInherited,
|
||||
skipForkedRepos,
|
||||
restrictUsersToGroup,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user