Skip to content

Commit

Permalink
Support maintainer token flows through rest of the app
Browse files Browse the repository at this point in the history
  • Loading branch information
subbuvenk-atlas committed Dec 12, 2024
1 parent 8e05386 commit c8cca23
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 16 deletions.
10 changes: 8 additions & 2 deletions src/client/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Deployment,
Environment,
GitlabPipelineStates,
GitLabAccessLevels,
} from '../types';
import { GitlabHttpMethodError, InvalidConfigFileError } from '../models/errors';
import { INVALID_YAML_ERROR } from '../models/error-messages';
Expand Down Expand Up @@ -287,12 +288,17 @@ export const getProjectBranch = async (
return branch;
};

export const getOwnedProjectsBySearchCriteria = async (
/**
* Get project data that the token role is at least a maintainer of, filtered by search criteria.
* @param search - search criteria i.e. project name
* @param groupToken - Gitlab access token
*/
export const getMaintainedProjectsBySearchCriteria = async (
search: string,
groupToken: string,
): Promise<GitlabAPIProject[]> => {
const params = {
owned: 'true',
min_access_level: GitLabAccessLevels.MAINTAINER.toString(),
search,
};

Expand Down
2 changes: 1 addition & 1 deletion src/resolvers/admin-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ resolver.define('groups/connect', async (req): Promise<ResolverResponse> => {
};
const groupId = await connectGroup(input);

await setupAndValidateWebhook(groupId, groupRole, webhookId, webhookSecretToken);
await setupAndValidateWebhook(groupId, webhookId, webhookSecretToken);

await graphqlGateway.compass.asApp().synchronizeLinkAssociations({
cloudId,
Expand Down
4 changes: 2 additions & 2 deletions src/services/data-provider-link-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ mockForgeApi();

import { mocked } from 'jest-mock';
import { generateGitlabProject } from '../__tests__/helpers/gitlab-helper';
import { getOwnedProjectsBySearchCriteria } from '../client/gitlab';
import { getMaintainedProjectsBySearchCriteria } from '../client/gitlab';
import { extractProjectInformation, getProjectDataFromUrl } from './data-provider-link-parser';
import { getGroupIds } from '../utils/storage-utils';

jest.mock('../client/gitlab');
jest.mock('../utils/storage-utils');

const mockedGetOwnedProjectsBySearchCriteria = mocked(getOwnedProjectsBySearchCriteria);
const mockedGetOwnedProjectsBySearchCriteria = mocked(getMaintainedProjectsBySearchCriteria);
const mockedGetGroupIds = mocked(getGroupIds);

const mockProjectUrl = 'https://gitlab.com/test/repo-name?testParam=test';
Expand Down
4 changes: 2 additions & 2 deletions src/services/data-provider-link-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { storage } from '@forge/api';
import parse from 'url-parse';

import { getOwnedProjectsBySearchCriteria } from '../client/gitlab';
import { getMaintainedProjectsBySearchCriteria } from '../client/gitlab';
import { STORAGE_SECRETS } from '../constants';
import { getGroupIds } from '../utils/storage-utils';
import { GitlabAPIProject } from '../types';
Expand Down Expand Up @@ -52,7 +52,7 @@ export const getProjectDataFromUrl = async (
const groupTokens = await getAllGroupTokens();

const projectsPromiseResults = await Promise.allSettled(
groupTokens.map((token) => getOwnedProjectsBySearchCriteria(projectName, token)),
groupTokens.map((token) => getMaintainedProjectsBySearchCriteria(projectName, token)),
);
const projectsResult = projectsPromiseResults.reduce<{ projects: GitlabAPIProject[]; projectIndex: number | null }>(
(result, currentProjectResult, index) => {
Expand Down
14 changes: 9 additions & 5 deletions src/services/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,34 +71,38 @@ const setupAndValidateForMaintainerToken = async (

export const setupAndValidateWebhook = async (
groupId: number,
tokenRole?: GitLabRoles,
webhookId?: number,
webhookSecretToken?: string,
): Promise<number> => {
console.log('Setting up webhook');
try {
const [existingWebhookResult, groupTokenResult] = await Promise.allSettled([
const [existingWebhookResult, tokenRoleResult, groupTokenResult] = await Promise.allSettled([
storage.get(`${STORAGE_KEYS.WEBHOOK_KEY_PREFIX}${groupId}`),
storage.get(`${STORAGE_KEYS.TOKEN_ROLE_PREFIX}${groupId}`),
storage.getSecret(`${STORAGE_SECRETS.GROUP_TOKEN_KEY_PREFIX}${groupId}`),
]);

if (
existingWebhookResult.status === ALL_SETTLED_STATUS.REJECTED ||
tokenRoleResult.status === ALL_SETTLED_STATUS.REJECTED ||
groupTokenResult.status === ALL_SETTLED_STATUS.REJECTED
) {
throw new Error(
`Error getting existing webhook or group token: ${getFormattedErrors([
`Error getting existing webhook, token role or group token: ${getFormattedErrors([
existingWebhookResult,
tokenRoleResult,
groupTokenResult,
])}`,
);
}

const existingWebhook = existingWebhookResult.value;
const groupToken = groupTokenResult.value;
const tokenRole: string = tokenRoleResult.value;

console.log('INFO: Setting up webhook', groupId, tokenRole, webhookId, webhookSecretToken);

// One of the pathways calling this is for fetching connectedGroups
// in which case, the tokenRole could be empty.
// Legacy integrations might not have the new tokenRole stored. These are treated as OWNER.
if (!tokenRole || tokenRole === GitLabRoles.OWNER) {
return setupAndValidateForOwnerToken(groupId, existingWebhook, groupToken);
}
Expand Down
10 changes: 6 additions & 4 deletions ui/src/services/invokes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
FeaturesList,
GroupProjectsResponse,
} from '../resolverTypes';
import { TeamsWithMembershipStatus } from '../types';
import { GitLabRoles, TeamsWithMembershipStatus } from '../types';

export const disconnectGroup = (id: number): Promise<ResolverResponse> => {
return invoke<ResolverResponse>('groups/disconnect', {
Expand All @@ -29,15 +29,17 @@ export const getAllExistingGroups = (): Promise<ResolverResponse<GitlabAPIGroup[
export const connectGroup = (
groupToken: string,
groupTokenName: string,
groupRole: string,
groupName: string,
webhookSecretToken: string,
groupName?: string,
webhookSecretToken?: string,
webhookId?: number,
groupRole: string = GitLabRoles.OWNER,
): Promise<ResolverResponse> => {
return invoke<ResolverResponse>('groups/connect', {
groupToken,
groupTokenName,
groupRole,
groupName,
webhookId,
webhookSecretToken,
});
};
Expand Down

0 comments on commit c8cca23

Please sign in to comment.