Merge pull request #20145 from matteosilv/master
email field and external uid annotation for GitlabOrgDiscoveryEntityProvider Enterprise users
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
fix: use REST API to get root group memberships for GitLab SaaS users listing
|
||||
|
||||
This API is the only one that shows `email` field for enterprise users and
|
||||
allows to filter out bot users not using a license using the `is_using_seat`
|
||||
field.
|
||||
|
||||
We also added the annotation `gitlab.com/saml-external-uid` taking the value
|
||||
of `group_saml_identity.extern_uid` of the `groups/:group-id/members` endpoint
|
||||
response. This is useful in case you want to create a `SignInResolver` that
|
||||
references the user with the id of your identity provider (e.g. OneLogin).
|
||||
|
||||
ref:
|
||||
|
||||
https://docs.gitlab.com/ee/user/enterprise_user/#get-users-email-addresses-through-the-api
|
||||
https://docs.gitlab.com/ee/api/members.html#limitations
|
||||
@@ -421,7 +421,7 @@ describe('GitLabClient', () => {
|
||||
user: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
username: 'user1',
|
||||
commitEmail: 'user1@example.com',
|
||||
publicEmail: 'user1@example.com',
|
||||
name: 'user1',
|
||||
state: 'active',
|
||||
webUrl: 'user1.com',
|
||||
@@ -525,7 +525,7 @@ describe('GitLabClient', () => {
|
||||
user: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
username: 'user1',
|
||||
commitEmail: 'user1@example.com',
|
||||
publicEmail: 'user1@example.com',
|
||||
name: 'user1',
|
||||
state: 'active',
|
||||
webUrl: 'user1.com',
|
||||
@@ -538,7 +538,7 @@ describe('GitLabClient', () => {
|
||||
user: {
|
||||
id: 'gid://gitlab/User/2',
|
||||
username: 'user2',
|
||||
commitEmail: 'user2@example.com',
|
||||
publicEmail: 'user2@example.com',
|
||||
name: 'user2',
|
||||
state: 'active',
|
||||
webUrl: 'user2.com',
|
||||
@@ -772,7 +772,7 @@ describe('GitLabClient', () => {
|
||||
user: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
username: 'user1',
|
||||
commitEmail: 'user1@example.com',
|
||||
publicEmail: 'user1@example.com',
|
||||
name: 'user1',
|
||||
state: 'active',
|
||||
webUrl: 'user1.com',
|
||||
|
||||
@@ -91,6 +91,22 @@ export class GitLabClient {
|
||||
});
|
||||
}
|
||||
|
||||
async listSaaSUsers(
|
||||
groupPath: string,
|
||||
options?: CommonListOptions,
|
||||
): Promise<PagedResponse<GitLabUser>> {
|
||||
return this.pagedRequest(
|
||||
`/groups/${encodeURIComponent(groupPath)}/members`,
|
||||
{
|
||||
...options,
|
||||
show_seat_info: true,
|
||||
},
|
||||
).then(resp => {
|
||||
resp.items = resp.items.filter(user => user.is_using_seat);
|
||||
return resp;
|
||||
});
|
||||
}
|
||||
|
||||
async listGroups(
|
||||
options?: CommonListOptions,
|
||||
): Promise<PagedResponse<GitLabGroup>> {
|
||||
@@ -205,7 +221,7 @@ export class GitLabClient {
|
||||
user {
|
||||
id
|
||||
username
|
||||
commitEmail
|
||||
publicEmail
|
||||
name
|
||||
state
|
||||
webUrl
|
||||
@@ -240,7 +256,7 @@ export class GitLabClient {
|
||||
const formattedUserResponse = {
|
||||
id: Number(userItem.user.id.replace(/^gid:\/\/gitlab\/User\//, '')),
|
||||
username: userItem.user.username,
|
||||
email: userItem.user.commitEmail,
|
||||
email: userItem.user.publicEmail,
|
||||
name: userItem.user.name,
|
||||
state: userItem.user.state,
|
||||
web_url: userItem.user.webUrl,
|
||||
|
||||
@@ -39,12 +39,17 @@ export type GitLabProject = {
|
||||
export type GitLabUser = {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
email?: string;
|
||||
name: string;
|
||||
state: string;
|
||||
web_url: string;
|
||||
avatar_url: string;
|
||||
groups?: GitLabGroup[];
|
||||
group_saml_identity?: GitLabGroupSamlIdentity;
|
||||
};
|
||||
|
||||
export type GitLabGroupSamlIdentity = {
|
||||
extern_uid: string;
|
||||
};
|
||||
|
||||
export type GitLabGroup = {
|
||||
@@ -64,7 +69,7 @@ export type GitLabGroupMembersResponse = {
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
commitEmail: string;
|
||||
publicEmail: string;
|
||||
name: string;
|
||||
state: string;
|
||||
webUrl: string;
|
||||
|
||||
+65
-42
@@ -548,46 +548,6 @@ describe('GitlabOrgDiscoveryEntityProvider', () => {
|
||||
}),
|
||||
),
|
||||
),
|
||||
graphql
|
||||
.link('https://gitlab.com/api/graphql')
|
||||
.query('getGroupMembers', async (_, res, ctx) =>
|
||||
res(
|
||||
ctx.data({
|
||||
group: {
|
||||
groupMembers: {
|
||||
nodes: [
|
||||
{
|
||||
user: {
|
||||
id: 'gid://gitlab/User/12',
|
||||
username: 'testuser1',
|
||||
commitEmail: 'testuser1@example.com',
|
||||
state: 'active',
|
||||
name: 'Test User 1',
|
||||
webUrl: 'https://gitlab.com/testuser1',
|
||||
avatarUrl: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
user: {
|
||||
id: 'gid://gitlab/User/34',
|
||||
username: 'testuser2',
|
||||
commitEmail: 'testuser2@example.com',
|
||||
state: 'active',
|
||||
name: 'Test User 2',
|
||||
webUrl: 'https://gitlab.com/testuser2',
|
||||
avatarUrl: 'https://secure.gravatar.com/',
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: 'end',
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
graphql
|
||||
.link('https://gitlab.com/api/graphql')
|
||||
.query('getGroupMembers', async (req, res, ctx) =>
|
||||
@@ -608,6 +568,67 @@ describe('GitlabOrgDiscoveryEntityProvider', () => {
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
`https://gitlab.com/api/v4/groups/group1/members`,
|
||||
(_req, res, ctx) => {
|
||||
const response = [
|
||||
{
|
||||
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: 30,
|
||||
created_at: '2023-07-19T08:58:34.984Z',
|
||||
expires_at: null,
|
||||
id: 34,
|
||||
username: 'testuser2',
|
||||
name: 'Test User 2',
|
||||
state: 'active',
|
||||
avatar_url: 'https://secure.gravatar.com/',
|
||||
web_url: 'https://gitlab.com/testuser2',
|
||||
email: 'testuser2@example.com',
|
||||
group_saml_identity: {
|
||||
provider: 'group_saml',
|
||||
extern_uid: '52',
|
||||
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',
|
||||
},
|
||||
];
|
||||
return res(ctx.json(response));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
@@ -630,11 +651,12 @@ describe('GitlabOrgDiscoveryEntityProvider', () => {
|
||||
'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: ['group2', 'group3'],
|
||||
memberOf: ['group2'],
|
||||
profile: {
|
||||
displayName: 'Test User 1',
|
||||
email: 'testuser1@example.com',
|
||||
@@ -655,11 +677,12 @@ describe('GitlabOrgDiscoveryEntityProvider', () => {
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'url:https://gitlab.com/testuser2',
|
||||
'gitlab.com/user-login': 'https://gitlab.com/testuser2',
|
||||
'gitlab.com/saml-external-uid': '52',
|
||||
},
|
||||
name: 'testuser2',
|
||||
},
|
||||
spec: {
|
||||
memberOf: ['group2', 'group3'],
|
||||
memberOf: ['group3'],
|
||||
profile: {
|
||||
displayName: 'Test User 2',
|
||||
email: 'testuser2@example.com',
|
||||
|
||||
+12
-6
@@ -193,12 +193,14 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
});
|
||||
} else {
|
||||
groups = (await client.listDescendantGroups(this.config.group)).items;
|
||||
users = (
|
||||
await client.getGroupMembers(this.config.group.split('/')[0], [
|
||||
'DIRECT',
|
||||
'DESCENDANTS',
|
||||
])
|
||||
).items;
|
||||
const rootGroup = this.config.group.split('/')[0];
|
||||
users = paginated<GitLabUser>(
|
||||
options => client.listSaaSUsers(rootGroup, options),
|
||||
{
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const idMappedUser: { [userId: number]: GitLabUser } = {};
|
||||
@@ -332,6 +334,10 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
const annotations: { [annotationName: string]: string } = {};
|
||||
|
||||
annotations[`${host}/user-login`] = user.web_url;
|
||||
if (user?.group_saml_identity?.extern_uid) {
|
||||
annotations[`${host}/saml-external-uid`] =
|
||||
user.group_saml_identity.extern_uid;
|
||||
}
|
||||
|
||||
const entity: UserEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
|
||||
Reference in New Issue
Block a user