diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/bitbucket-secret-scanning-router.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/bitbucket-secret-scanning-router.ts new file mode 100644 index 000000000..8aa7887e4 --- /dev/null +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/bitbucket-secret-scanning-router.ts @@ -0,0 +1,16 @@ +import { registerSecretScanningEndpoints } from "@app/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints"; +import { + BitBucketDataSourceSchema, + CreateBitBucketDataSourceSchema, + UpdateBitBucketDataSourceSchema +} from "@app/ee/services/secret-scanning-v2/bitbucket"; +import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; + +export const registerBitBucketSecretScanningRouter = async (server: FastifyZodProvider) => + registerSecretScanningEndpoints({ + type: SecretScanningDataSource.BitBucket, + server, + responseSchema: BitBucketDataSourceSchema, + createSchema: CreateBitBucketDataSourceSchema, + updateSchema: UpdateBitBucketDataSourceSchema + }); diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/index.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/index.ts index 703529947..ff7c304f6 100644 --- a/backend/src/ee/routes/v2/secret-scanning-v2-routers/index.ts +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/index.ts @@ -1,5 +1,6 @@ import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { registerBitBucketSecretScanningRouter } from "./bitbucket-secret-scanning-router"; import { registerGitHubSecretScanningRouter } from "./github-secret-scanning-router"; export * from "./secret-scanning-v2-router"; @@ -8,5 +9,6 @@ export const SECRET_SCANNING_REGISTER_ROUTER_MAP: Record< SecretScanningDataSource, (server: FastifyZodProvider) => Promise > = { - [SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter + [SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter, + [SecretScanningDataSource.BitBucket]: registerBitBucketSecretScanningRouter }; diff --git a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts index 929a60df5..ed7b66a19 100644 --- a/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts +++ b/backend/src/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretScanningConfigsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { BitBucketDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/bitbucket"; import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github"; import { SecretScanningFindingStatus, @@ -21,7 +22,10 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [GitHubDataSourceListItemSchema]); +const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [ + GitHubDataSourceListItemSchema, + BitBucketDataSourceListItemSchema +]); export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => { server.route({ diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-constants.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-constants.ts new file mode 100644 index 000000000..0b9335915 --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-constants.ts @@ -0,0 +1,9 @@ +import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { TSecretScanningDataSourceListItem } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +export const BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION: TSecretScanningDataSourceListItem = { + name: "BitBucket", + type: SecretScanningDataSource.BitBucket, + connection: AppConnection.BitBucket +}; diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts new file mode 100644 index 000000000..29ffaffaa --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts @@ -0,0 +1,252 @@ +import { join } from "path"; + +import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; +import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types"; +import { + SecretScanningDataSource, + SecretScanningFindingSeverity, + SecretScanningResource +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { + cloneRepository, + convertPatchLineToFileLineNumber, + replaceNonChangesWithNewlines +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns"; +import { + TSecretScanningFactoryGetDiffScanFindingsPayload, + TSecretScanningFactoryGetDiffScanResourcePayload, + TSecretScanningFactoryGetFullScanPath, + TSecretScanningFactoryInitialize, + TSecretScanningFactoryListRawResources, + TSecretScanningFactoryPostInitialization +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { titleCaseToCamelCase } from "@app/lib/fn"; +import { GitHubRepositoryRegex } from "@app/lib/regex"; +import { + getBitBucketUser, + listBitBucketRepositories, + TBitBucketConnection +} from "@app/services/app-connection/bitbucket"; + +import { TBitBucketDataSourceWithConnection, TQueueBitBucketResourceDiffScan } from "./bitbucket-secret-scanning-types"; + +export const BitBucketSecretScanningFactory = () => { + const initialize: TSecretScanningFactoryInitialize = async ( + { connection, secretScanningV2DAL }, + callback + ) => { + // TODO(andrey): Swap for something proper + const externalId = connection.credentials.email; + + const existingDataSource = await secretScanningV2DAL.dataSources.findOne({ + externalId, + type: SecretScanningDataSource.BitBucket + }); + + if (existingDataSource) + throw new BadRequestError({ + message: `A Data Source already exists for this BitBucket Radar Connection in the Project with ID "${existingDataSource.projectId}"` + }); + + return callback({ + externalId + }); + }; + + const postInitialization: TSecretScanningFactoryPostInitialization = async () => { + // no post-initialization required + }; + + const listRawResources: TSecretScanningFactoryListRawResources = async ( + dataSource + ) => { + const { + connection, + config: { includeRepos } + } = dataSource; + + const repos = await listBitBucketRepositories(connection); + + const filteredRepos: typeof repos = []; + if (includeRepos.includes("*")) { + filteredRepos.push(...repos); + } else { + filteredRepos.push(...repos.filter((repo) => includeRepos.includes(repo.full_name))); + } + + return filteredRepos.map(({ slug, full_name }) => ({ + name: full_name, + externalId: slug.toString(), + type: SecretScanningResource.Repository + })); + }; + + // TODO(andrey): Finish + const getFullScanPath: TSecretScanningFactoryGetFullScanPath = async ({ + dataSource, + resourceName, + tempFolder + }) => { + const { + connection: { + credentials: { apiToken, email } + } + } = dataSource; + + const repoPath = join(tempFolder, "repo.git"); + + if (!GitHubRepositoryRegex.test(resourceName)) { + throw new Error("Invalid BitBucket repository name"); + } + + const { username } = await getBitBucketUser({ email, apiToken }); + + await cloneRepository({ + cloneUrl: `https://${encodeURIComponent(username)}:${apiToken}@bitbucket.org/${resourceName}.git`, + repoPath + }); + + return repoPath; + }; + + const getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload< + TQueueBitBucketResourceDiffScan["payload"] + > = ({ repository }) => { + return { + name: repository.full_name, + externalId: repository.id.toString(), + type: SecretScanningResource.Repository + }; + }; + + const getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload< + TBitBucketDataSourceWithConnection, + TQueueBitBucketResourceDiffScan["payload"] + > = async ({ dataSource, payload, resourceName, configPath }) => { + const { + connection: { + credentials: { apiToken, email } + } + } = dataSource; + + console.log("getDiffScanFindingsPayload"); + + const { commits, repository } = payload; + + const allFindings: SecretMatch[] = []; + + const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`; + + for (const commit of commits) { + // eslint-disable-next-line no-await-in-loop + const { data: diffstat } = await request.get<{ + values: { + status: "added" | "modified" | "removed" | "renamed"; + new?: { path: string }; + old?: { path: string }; + }[]; + }>(`https://api.bitbucket.org/2.0/repositories/${repository.full_name}/diffstat/${commit.id}`, { + headers: { + Authorization: authHeader, + Accept: "application/json" + } + }); + + // eslint-disable-next-line no-continue + if (!diffstat.values) continue; + + for (const file of diffstat.values) { + if ((file.status === "added" || file.status === "modified") && file.new?.path) { + const filePath = file.new.path; + + // eslint-disable-next-line no-await-in-loop + const { data: patch } = await request.get( + `https://api.bitbucket.org/2.0/repositories/${repository.full_name}/diff/${commit.id}`, + { + params: { + path: filePath + }, + headers: { + Authorization: authHeader + }, + responseType: "text" + } + ); + + console.log(1); + + // eslint-disable-next-line no-continue + if (!patch) continue; + console.log(2); + + // eslint-disable-next-line + const findings = await scanContentAndGetFindings(replaceNonChangesWithNewlines(`\n${patch}`), configPath); + console.log(3); + console.log(findings); + + const adjustedFindings = findings.map((finding) => { + const startLine = convertPatchLineToFileLineNumber(patch, finding.StartLine); + const endLine = + finding.StartLine === finding.EndLine + ? startLine + : convertPatchLineToFileLineNumber(patch, finding.EndLine); + const startColumn = finding.StartColumn - 1; // subtract 1 for + + const endColumn = finding.EndColumn - 1; // subtract 1 for + + + console.log("finding"); + console.log(finding.Link); + + return { + ...finding, + StartLine: startLine, + EndLine: endLine, + StartColumn: startColumn, + EndColumn: endColumn, + File: filePath, + Commit: commit.id, + Author: commit.author.name, + Email: commit.author.email ?? "", + Message: commit.message, + Fingerprint: `${commit.id}:${filePath}:${finding.RuleID}:${startLine}:${startColumn}`, + Date: commit.timestamp, + Link: `https://bitbucket.org/${resourceName}/src/${commit.id}/${filePath}#lines-${startLine}` + }; + }); + + console.log("adjusted"); + console.log(adjustedFindings); + + allFindings.push(...adjustedFindings); + } + } + } + + console.log("HEREEE"); + console.log(allFindings); + + return allFindings.map( + ({ + // discard match and secret as we don't want to store + Match, + Secret, + ...finding + }) => ({ + details: titleCaseToCamelCase(finding), + fingerprint: finding.Fingerprint, + severity: SecretScanningFindingSeverity.High, + rule: finding.RuleID + }) + ); + }; + + return { + initialize, + postInitialization, + listRawResources, + getFullScanPath, + getDiffScanResourcePayload, + getDiffScanFindingsPayload + }; +}; diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts new file mode 100644 index 000000000..830b5c5be --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts @@ -0,0 +1,87 @@ +import { z } from "zod"; + +import { + SecretScanningDataSource, + SecretScanningResource +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { + BaseCreateSecretScanningDataSourceSchema, + BaseSecretScanningDataSourceSchema, + BaseSecretScanningFindingSchema, + BaseUpdateSecretScanningDataSourceSchema, + GitRepositoryScanFindingDetailsSchema +} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-schemas"; +import { SecretScanningDataSources } from "@app/lib/api-docs"; +import { GitHubRepositoryRegex } from "@app/lib/regex"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +export const BitBucketDataSourceConfigSchema = z.object({ + includeRepos: z + .array( + z + .string() + .min(1) + .max(256) + .refine((value) => value === "*" || GitHubRepositoryRegex.test(value), "Invalid repository name format") + ) + .nonempty("One or more repositories required") + .max(100, "Cannot configure more than 100 repositories") + .default(["*"]) + .describe(SecretScanningDataSources.CONFIG.BITBUCKET.includeRepos) +}); + +export const BitBucketDataSourceSchema = BaseSecretScanningDataSourceSchema({ + type: SecretScanningDataSource.BitBucket, + isConnectionRequired: true +}) + .extend({ + config: BitBucketDataSourceConfigSchema + }) + .describe( + JSON.stringify({ + title: "BitBucket" + }) + ); + +export const CreateBitBucketDataSourceSchema = BaseCreateSecretScanningDataSourceSchema({ + type: SecretScanningDataSource.BitBucket, + isConnectionRequired: true +}) + .extend({ + config: BitBucketDataSourceConfigSchema + }) + .describe( + JSON.stringify({ + title: "BitBucket" + }) + ); + +export const UpdateBitBucketDataSourceSchema = BaseUpdateSecretScanningDataSourceSchema( + SecretScanningDataSource.BitBucket +) + .extend({ + config: BitBucketDataSourceConfigSchema.optional() + }) + .describe( + JSON.stringify({ + title: "BitBucket" + }) + ); + +export const BitBucketDataSourceListItemSchema = z + .object({ + name: z.literal("BitBucket"), + connection: z.literal(AppConnection.BitBucket), + type: z.literal(SecretScanningDataSource.BitBucket) + }) + .describe( + JSON.stringify({ + title: "BitBucket" + }) + ); + +export const BitBucketFindingSchema = BaseSecretScanningFindingSchema.extend({ + resourceType: z.literal(SecretScanningResource.Repository), + dataSourceType: z.literal(SecretScanningDataSource.BitBucket), + details: GitRepositoryScanFindingDetailsSchema +}); diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts new file mode 100644 index 000000000..508aef529 --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts @@ -0,0 +1,87 @@ +import { PushEvent } from "@octokit/webhooks-types"; + +import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; +import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { logger } from "@app/lib/logger"; + +import { TBitBucketDataSource } from "./bitbucket-secret-scanning-types"; + +export const bitBucketSecretScanningService = ( + secretScanningV2DAL: TSecretScanningV2DALFactory, + secretScanningV2Queue: Pick +) => { + const handleInstallationDeletedEvent = async (installationId: number) => { + const dataSource = await secretScanningV2DAL.dataSources.findOne({ + externalId: String(installationId), + type: SecretScanningDataSource.BitBucket + }); + + if (!dataSource) { + logger.error( + `secretScanningV2RemoveEvent: BitBucket - Could not find data source [installationId=${installationId}]` + ); + return; + } + + logger.info( + `secretScanningV2RemoveEvent: BitBucket - installation deleted [installationId=${installationId}] [dataSourceId=${dataSource.id}]` + ); + + await secretScanningV2DAL.dataSources.updateById(dataSource.id, { + isDisconnected: true + }); + }; + + const handlePushEvent = async (payload: PushEvent) => { + const { commits, repository, installation } = payload; + + if (!commits || !repository || !installation) { + logger.warn( + `secretScanningV2PushEvent: BitBucket - Insufficient data [commits=${commits?.length ?? 0}] [repository=${repository.name}] [installationId=${installation?.id}]` + ); + return; + } + + const dataSource = (await secretScanningV2DAL.dataSources.findOne({ + externalId: String(installation.id), + type: SecretScanningDataSource.BitBucket + })) as TBitBucketDataSource | undefined; + + if (!dataSource) { + logger.error( + `secretScanningV2PushEvent: BitBucket - Could not find data source [installationId=${installation.id}]` + ); + return; + } + + const { + isAutoScanEnabled, + config: { includeRepos } + } = dataSource; + + if (!isAutoScanEnabled) { + logger.info( + `secretScanningV2PushEvent: BitBucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [installationId=${installation.id}]` + ); + return; + } + + if (includeRepos.includes("*") || includeRepos.includes(repository.full_name)) { + await secretScanningV2Queue.queueResourceDiffScan({ + dataSourceType: SecretScanningDataSource.BitBucket, + payload, + dataSourceId: dataSource.id + }); + } else { + logger.info( + `secretScanningV2PushEvent: BitBucket - ignoring due to repository not being present in config [installationId=${installation.id}] [dataSourceId=${dataSource.id}]` + ); + } + }; + + return { + handlePushEvent, + handleInstallationDeletedEvent + }; +}; diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types.ts new file mode 100644 index 000000000..5df73cbda --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types.ts @@ -0,0 +1,32 @@ +import { PushEvent } from "@octokit/webhooks-types"; +import { z } from "zod"; + +import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; +import { TBitBucketConnection } from "@app/services/app-connection/bitbucket"; + +import { + BitBucketDataSourceListItemSchema, + BitBucketDataSourceSchema, + BitBucketFindingSchema, + CreateBitBucketDataSourceSchema +} from "./bitbucket-secret-scanning-schemas"; + +export type TBitBucketDataSource = z.infer; + +export type TBitBucketDataSourceInput = z.infer; + +export type TBitBucketDataSourceListItem = z.infer; + +export type TBitBucketFinding = z.infer; + +export type TBitBucketDataSourceWithConnection = TBitBucketDataSource & { + connection: TBitBucketConnection; +}; + +export type TQueueBitBucketResourceDiffScan = { + dataSourceType: SecretScanningDataSource.BitBucket; + payload: PushEvent; + dataSourceId: string; + resourceId: string; + scanId: string; +}; diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/index.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/index.ts new file mode 100644 index 000000000..5ac8262f3 --- /dev/null +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/index.ts @@ -0,0 +1,3 @@ +export * from "./bitbucket-secret-scanning-constants"; +export * from "./bitbucket-secret-scanning-schemas"; +export * from "./bitbucket-secret-scanning-types"; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-enums.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-enums.ts index 082f3d760..91ce3c8c4 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-enums.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-enums.ts @@ -1,5 +1,6 @@ export enum SecretScanningDataSource { - GitHub = "github" + GitHub = "github", + BitBucket = "bitbucket" } export enum SecretScanningScanStatus { diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-factory.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-factory.ts index 109afe5f3..a0f7d6c65 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-factory.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-factory.ts @@ -1,3 +1,4 @@ +import { BitBucketSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory"; import { GitHubSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/github/github-secret-scanning-factory"; import { SecretScanningDataSource } from "./secret-scanning-v2-enums"; @@ -15,5 +16,6 @@ type TSecretScanningFactoryImplementation = TSecretScanningFactory< >; export const SECRET_SCANNING_FACTORY_MAP: Record = { - [SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation + [SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation, + [SecretScanningDataSource.BitBucket]: BitBucketSecretScanningFactory as TSecretScanningFactoryImplementation }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts index 64a0ba4ed..8a3729c1c 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-fns.ts @@ -4,6 +4,7 @@ import RE2 from "re2"; import { readFindingsFile } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types"; +import { BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/bitbucket"; import { GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/github"; import { titleCaseToCamelCase } from "@app/lib/fn"; @@ -11,7 +12,8 @@ import { SecretScanningDataSource, SecretScanningFindingSeverity } from "./secre import { TCloneRepository, TGetFindingsPayload, TSecretScanningDataSourceListItem } from "./secret-scanning-v2-types"; const SECRET_SCANNING_SOURCE_LIST_OPTIONS: Record = { - [SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION + [SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION, + [SecretScanningDataSource.BitBucket]: BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION }; export const listSecretScanningDataSourceOptions = () => { diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-maps.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-maps.ts index f41a2b5c2..d5668b06a 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-maps.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-maps.ts @@ -2,13 +2,17 @@ import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/se import { AppConnection } from "@app/services/app-connection/app-connection-enums"; export const SECRET_SCANNING_DATA_SOURCE_NAME_MAP: Record = { - [SecretScanningDataSource.GitHub]: "GitHub" + [SecretScanningDataSource.GitHub]: "GitHub", + [SecretScanningDataSource.BitBucket]: "BitBucket" }; export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record = { - [SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar + [SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar, + [SecretScanningDataSource.BitBucket]: AppConnection.BitBucket }; export const AUTO_SYNC_DESCRIPTION_HELPER: Record = { - [SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" } + [SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" }, + // TODO(andrey): May need change + [SecretScanningDataSource.BitBucket]: { verb: "push", noun: "repositories" } }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts index f1f09506f..f84254a50 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts @@ -49,6 +49,7 @@ import { TAppConnection } from "@app/services/app-connection/app-connection-type import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { bitBucketSecretScanningService } from "./bitbucket/bitbucket-secret-scanning-service"; import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal"; import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue"; @@ -869,6 +870,7 @@ export const secretScanningV2ServiceFactory = ({ updateSecretScanningFindingById, findSecretScanningConfigByProjectId, upsertSecretScanningConfig, - github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue) + github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue), + bitbucket: bitBucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue) }; }; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts index 3ee5851d7..3342a8906 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-types.ts @@ -4,6 +4,14 @@ import { TSecretScanningResources, TSecretScanningScans } from "@app/db/schemas"; +import { + TBitBucketDataSource, + TBitBucketDataSourceInput, + TBitBucketDataSourceListItem, + TBitBucketDataSourceWithConnection, + TBitBucketFinding, + TQueueBitBucketResourceDiffScan +} from "@app/ee/services/secret-scanning-v2/bitbucket"; import { TGitHubDataSource, TGitHubDataSourceInput, @@ -19,7 +27,7 @@ import { SecretScanningScanStatus } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; -export type TSecretScanningDataSource = TGitHubDataSource; +export type TSecretScanningDataSource = TGitHubDataSource | TBitBucketDataSource; export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & { lastScannedAt?: Date | null; @@ -41,13 +49,15 @@ export type TSecretScanningScanWithDetails = TSecretScanningScans & { resourceName: string; }; -export type TSecretScanningDataSourceWithConnection = TGitHubDataSourceWithConnection; +export type TSecretScanningDataSourceWithConnection = + | TGitHubDataSourceWithConnection + | TBitBucketDataSourceWithConnection; -export type TSecretScanningDataSourceInput = TGitHubDataSourceInput; +export type TSecretScanningDataSourceInput = TGitHubDataSourceInput | TBitBucketDataSourceInput; -export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem; +export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem | TBitBucketDataSourceListItem; -export type TSecretScanningFinding = TGitHubFinding; +export type TSecretScanningFinding = TGitHubFinding | TBitBucketFinding; export type TListSecretScanningDataSourcesByProjectId = { projectId: string; @@ -99,7 +109,7 @@ export type TQueueSecretScanningDataSourceFullScan = { scanId: string; }; -export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan; +export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan | TQueueBitBucketResourceDiffScan; export type TQueueSecretScanningSendNotification = { dataSource: TSecretScanningDataSources; diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-union-schemas.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-union-schemas.ts index 4f34791f8..b030273de 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-union-schemas.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-union-schemas.ts @@ -1,7 +1,14 @@ import { z } from "zod"; +import { BitBucketDataSourceSchema, BitBucketFindingSchema } from "@app/ee/services/secret-scanning-v2/bitbucket"; import { GitHubDataSourceSchema, GitHubFindingSchema } from "@app/ee/services/secret-scanning-v2/github"; -export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [GitHubDataSourceSchema]); +export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [ + GitHubDataSourceSchema, + BitBucketDataSourceSchema +]); -export const SecretScanningFindingSchema = z.discriminatedUnion("resourceType", [GitHubFindingSchema]); +export const SecretScanningFindingSchema = z.discriminatedUnion("dataSourceType", [ + GitHubFindingSchema, + BitBucketFindingSchema +]); diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/BitBucketDataSourceConfigFields.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/BitBucketDataSourceConfigFields.tsx new file mode 100644 index 000000000..f215e41ca --- /dev/null +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/BitBucketDataSourceConfigFields.tsx @@ -0,0 +1,122 @@ +import { useEffect } from "react"; +import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { MultiValue } from "react-select"; +import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { FilterableSelect, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2"; +import { + TBitBucketRepo, + useBitBucketConnectionListRepositories +} from "@app/hooks/api/appConnections/bitbucket"; +import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; + +import { TSecretScanningDataSourceForm } from "../schemas"; +import { SecretScanningDataSourceConnectionField } from "../SecretScanningDataSourceConnectionField"; + +enum ScanMethod { + AllRepositories = "all-repositories", + SelectRepositories = "select-repositories" +} + +export const BitBucketDataSourceConfigFields = () => { + const { control, watch, setValue } = useFormContext< + TSecretScanningDataSourceForm & { + type: SecretScanningDataSource.BitBucket; + } + >(); + + const connectionId = useWatch({ control, name: "connection.id" }); + const isUpdate = Boolean(watch("id")); + + const { data: repositories, isPending: areRepositoriesLoading } = + useBitBucketConnectionListRepositories(connectionId, { enabled: Boolean(connectionId) }); + + const includeRepos = watch("config.includeRepos"); + + const scanMethod = + !includeRepos || includeRepos[0] === "*" + ? ScanMethod.AllRepositories + : ScanMethod.SelectRepositories; + + useEffect(() => { + if (!includeRepos) { + setValue("config.includeRepos", ["*"]); + } + }, [includeRepos, setValue]); + + return ( + <> + { + if (scanMethod === ScanMethod.SelectRepositories) { + setValue("config.includeRepos", []); + } + }} + /> + + + + {scanMethod === ScanMethod.SelectRepositories && ( + ( + Ensure that your connection has the correct permissions.} + > +
+ Don't see the repository you're looking for?{" "} + +
+ + } + > + value.includes(repository.name))} + onChange={(newValue) => { + onChange( + newValue ? (newValue as MultiValue).map((p) => p.name) : null + ); + }} + options={repositories} + placeholder="Select repositories..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.name} + /> +
+ )} + /> + )} + + ); +}; diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/SecretScanningDataSourceConfigFields.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/SecretScanningDataSourceConfigFields.tsx index bebcf28fc..157e614e0 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/SecretScanningDataSourceConfigFields.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceConfigFields/SecretScanningDataSourceConfigFields.tsx @@ -5,10 +5,12 @@ import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2"; import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; import { TSecretScanningDataSourceForm } from "../schemas"; +import { BitBucketDataSourceConfigFields } from "./BitBucketDataSourceConfigFields"; import { GitHubDataSourceConfigFields } from "./GitHubDataSourceConfigFields"; const COMPONENT_MAP: Record = { - [SecretScanningDataSource.GitHub]: GitHubDataSourceConfigFields + [SecretScanningDataSource.GitHub]: GitHubDataSourceConfigFields, + [SecretScanningDataSource.BitBucket]: BitBucketDataSourceConfigFields }; export const SecretScanningDataSourceConfigFields = () => { diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/BitBucketDataSourceReviewFields.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/BitBucketDataSourceReviewFields.tsx new file mode 100644 index 000000000..76855454a --- /dev/null +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/BitBucketDataSourceReviewFields.tsx @@ -0,0 +1,27 @@ +import { useFormContext } from "react-hook-form"; + +import { GenericFieldLabel } from "@app/components/v2"; +import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; + +import { TSecretScanningDataSourceForm } from "../schemas"; +import { SecretScanningDataSourceConfigReviewSection } from "./shared"; + +export const BitBucketDataSourceReviewFields = () => { + const { watch } = useFormContext< + TSecretScanningDataSourceForm & { + type: SecretScanningDataSource.BitBucket; + } + >(); + + const [{ includeRepos }, connection] = watch(["config", "connection"]); + const shouldScanAll = includeRepos[0] === "*"; + + return ( + + {connection && {connection.name}} + + {shouldScanAll ? "All" : includeRepos.join(", ")} + + + ); +}; diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/SecretScanningDataSourceReviewFields.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/SecretScanningDataSourceReviewFields.tsx index 021b5e679..79bab1cf7 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/SecretScanningDataSourceReviewFields.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceReviewFields/SecretScanningDataSourceReviewFields.tsx @@ -4,10 +4,12 @@ import { GenericFieldLabel } from "@app/components/v2"; import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; import { TSecretScanningDataSourceForm } from "../schemas"; +import { BitBucketDataSourceReviewFields } from "./BitBucketDataSourceReviewFields"; import { GitHubDataSourceReviewFields } from "./GitHubDataSourceReviewFields"; const COMPONENT_MAP: Record = { - [SecretScanningDataSource.GitHub]: GitHubDataSourceReviewFields + [SecretScanningDataSource.GitHub]: GitHubDataSourceReviewFields, + [SecretScanningDataSource.BitBucket]: BitBucketDataSourceReviewFields }; export const SecretScanningDataSourceReviewFields = () => { diff --git a/frontend/src/components/secret-scanning/forms/schemas/bitbucket-data-source-schema.ts b/frontend/src/components/secret-scanning/forms/schemas/bitbucket-data-source-schema.ts new file mode 100644 index 000000000..30c09a6fa --- /dev/null +++ b/frontend/src/components/secret-scanning/forms/schemas/bitbucket-data-source-schema.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; + +import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2"; + +import { BaseSecretScanningDataSourceSchema } from "./base-secret-scanning-data-source-schema"; + +export const BitBucketDataSourceSchema = z + .object({ + type: z.literal(SecretScanningDataSource.BitBucket), + config: z.object({ + includeRepos: z + .string() + .array() + .min(1, "One or more repositories required") + .max(100, "Cannot configure more than 100 repositories") + }) + }) + .merge(BaseSecretScanningDataSourceSchema({ isConnectionRequired: true })); diff --git a/frontend/src/components/secret-scanning/forms/schemas/index.ts b/frontend/src/components/secret-scanning/forms/schemas/index.ts index bfb1ae5ec..16345affd 100644 --- a/frontend/src/components/secret-scanning/forms/schemas/index.ts +++ b/frontend/src/components/secret-scanning/forms/schemas/index.ts @@ -1,9 +1,11 @@ import { z } from "zod"; +import { BitBucketDataSourceSchema } from "./bitbucket-data-source-schema"; import { GitHubDataSourceSchema } from "./github-data-source-schema"; export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [ - GitHubDataSourceSchema + GitHubDataSourceSchema, + BitBucketDataSourceSchema ]); export type TSecretScanningDataSourceForm = z.infer; diff --git a/frontend/src/helpers/secretScanningV2.ts b/frontend/src/helpers/secretScanningV2.ts index 877a57468..fd7a12f4e 100644 --- a/frontend/src/helpers/secretScanningV2.ts +++ b/frontend/src/helpers/secretScanningV2.ts @@ -19,6 +19,11 @@ export const SECRET_SCANNING_DATA_SOURCE_MAP: Record< name: "GitHub", image: "GitHub.png", size: 45 + }, + [SecretScanningDataSource.BitBucket]: { + name: "BitBucket", + image: "BitBucket.png", + size: 45 } }; @@ -26,7 +31,8 @@ export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record< SecretScanningDataSource, AppConnection > = { - [SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar + [SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar, + [SecretScanningDataSource.BitBucket]: AppConnection.BitBucket }; export const RESOURCE_DESCRIPTION_HELPER: Record< @@ -45,6 +51,13 @@ export const RESOURCE_DESCRIPTION_HELPER: Record< singularNoun: "repository", pluralTitle: "Repositories", singularTitle: "Repository" + }, + [SecretScanningDataSource.BitBucket]: { + verb: "push", + pluralNoun: "repositories", + singularNoun: "repository", + pluralTitle: "Repositories", + singularTitle: "Repository" } }; diff --git a/frontend/src/hooks/api/secretScanningV2/enums.ts b/frontend/src/hooks/api/secretScanningV2/enums.ts index 082f3d760..91ce3c8c4 100644 --- a/frontend/src/hooks/api/secretScanningV2/enums.ts +++ b/frontend/src/hooks/api/secretScanningV2/enums.ts @@ -1,5 +1,6 @@ export enum SecretScanningDataSource { - GitHub = "github" + GitHub = "github", + BitBucket = "bitbucket" } export enum SecretScanningScanStatus { diff --git a/frontend/src/hooks/api/secretScanningV2/types/bitbucket-data-source.ts b/frontend/src/hooks/api/secretScanningV2/types/bitbucket-data-source.ts new file mode 100644 index 000000000..5de2d0fcc --- /dev/null +++ b/frontend/src/hooks/api/secretScanningV2/types/bitbucket-data-source.ts @@ -0,0 +1,17 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { SecretScanningDataSource } from "../enums"; +import { TSecretScanningDataSourceBase } from "./shared"; + +export type TBitBucketDataSource = TSecretScanningDataSourceBase & { + type: SecretScanningDataSource.BitBucket; + config: { + includeRepos: string[]; + }; +}; + +export type TBitBucketDataSourceOption = { + name: string; + type: SecretScanningDataSource.BitBucket; + connection: AppConnection.BitBucket; +}; diff --git a/frontend/src/hooks/api/secretScanningV2/types/index.ts b/frontend/src/hooks/api/secretScanningV2/types/index.ts index 07beaef5e..5621f7b36 100644 --- a/frontend/src/hooks/api/secretScanningV2/types/index.ts +++ b/frontend/src/hooks/api/secretScanningV2/types/index.ts @@ -8,9 +8,10 @@ import { SecretScanningScanStatus, SecretScanningScanType } from "../enums"; +import { TBitBucketDataSource, TBitBucketDataSourceOption } from "./bitbucket-data-source"; import { TGitHubDataSource, TGitHubDataSourceOption } from "./github-data-source"; -export type TSecretScanningDataSource = TGitHubDataSource; +export type TSecretScanningDataSource = TGitHubDataSource | TBitBucketDataSource; export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & { lastScannedAt: string | null; @@ -23,7 +24,7 @@ export type TListSecretScanningDataSources = { dataSources: TSecretScanningDataSourceWithDetails[]; }; -export type TSecretScanningDataSourceOption = TGitHubDataSourceOption; +export type TSecretScanningDataSourceOption = TGitHubDataSourceOption | TBitBucketDataSourceOption; export type TListSecretScanningDataSourceOptions = { dataSourceOptions: TSecretScanningDataSourceOption[]; diff --git a/frontend/src/pages/project/layout.tsx b/frontend/src/pages/project/layout.tsx index f803329ca..8f85cb1a5 100644 --- a/frontend/src/pages/project/layout.tsx +++ b/frontend/src/pages/project/layout.tsx @@ -1,41 +1,37 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from "@tanstack/react-router"; -import { BreadcrumbTypes } from '@app/components/v2' -import { workspaceKeys } from '@app/hooks/api' -import { - fetchUserProjectPermissions, - roleQueryKeys, -} from '@app/hooks/api/roles/queries' -import { fetchWorkspaceById } from '@app/hooks/api/workspace/queries' -import { ProjectLayout } from '@app/layouts/ProjectLayout' -import { ProjectSelect } from '@app/layouts/ProjectLayout/components/ProjectSelect' +import { BreadcrumbTypes } from "@app/components/v2"; +import { workspaceKeys } from "@app/hooks/api"; +import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; +import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; +import { ProjectLayout } from "@app/layouts/ProjectLayout"; +import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout', + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout" )({ component: ProjectLayout, beforeLoad: async ({ params, context }) => { const project = await context.queryClient.ensureQueryData({ queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId), - }) + queryFn: () => fetchWorkspaceById(params.projectId) + }); await context.queryClient.ensureQueryData({ queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId, + workspaceId: params.projectId }), - queryFn: () => - fetchUserProjectPermissions({ workspaceId: params.projectId }), - }) + queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) + }); return { project, breadcrumbs: [ { type: BreadcrumbTypes.Component, - component: ProjectSelect, - }, - ], - } - }, -}) + component: ProjectSelect + } + ] + }; + } +}); diff --git a/frontend/src/pages/secret-manager/redirects/redirect-approval-page.tsx b/frontend/src/pages/secret-manager/redirects/redirect-approval-page.tsx index db60e8fa7..8fe2c51cf 100644 --- a/frontend/src/pages/secret-manager/redirects/redirect-approval-page.tsx +++ b/frontend/src/pages/secret-manager/redirects/redirect-approval-page.tsx @@ -1,14 +1,14 @@ -import { createFileRoute, redirect } from '@tanstack/react-router' +import { createFileRoute, redirect } from "@tanstack/react-router"; // this is done as part of migration for multi product inside project export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/approval', + "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/approval" )({ beforeLoad: ({ params, search }) => { throw redirect({ - to: '/projects/$projectId/secret-manager/approval', + to: "/projects/$projectId/secret-manager/approval", params, - search, - }) - }, -}) + search + }); + } +}); diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/BitBucketDataSourceConfigDisplay.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/BitBucketDataSourceConfigDisplay.tsx new file mode 100644 index 000000000..f23fa7e4a --- /dev/null +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/BitBucketDataSourceConfigDisplay.tsx @@ -0,0 +1,18 @@ +import { GenericFieldLabel } from "@app/components/v2"; +import { TBitBucketDataSource } from "@app/hooks/api/secretScanningV2/types/bitbucket-data-source"; + +type Props = { + dataSource: TBitBucketDataSource; +}; + +export const BitBucketDataSourceConfigDisplay = ({ dataSource }: Props) => { + const { + config: { includeRepos } + } = dataSource; + + return ( + + {includeRepos.includes("*") ? "All" : includeRepos.join(", ")} + + ); +}; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay.tsx index 90ebd84ce..b392ddf82 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/DataSourceConfigDisplay/DataSourceConfigDisplay.tsx @@ -4,6 +4,7 @@ import { } from "@app/hooks/api/secretScanningV2"; import { GitHubDataSourceConfigDisplay } from "./GitHubDataSourceConfigDisplay"; +import { BitBucketDataSourceConfigDisplay } from "./BitBucketDataSourceConfigDisplay"; type Props = { dataSource: TSecretScanningDataSource; @@ -13,6 +14,8 @@ export const DataSourceConfigDisplay = ({ dataSource }: Props) => { switch (dataSource.type) { case SecretScanningDataSource.GitHub: return ; + case SecretScanningDataSource.BitBucket: + return ; default: throw new Error( `Unhandled dataSource type ${(dataSource as TSecretScanningDataSource).type}`