mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 17:40:44 +00:00
Merge pull request #3925 from Infisical/ENG-3041
feat(secret-scanning): Bitbucket data source + App Connection
This commit is contained in:
+5
@@ -16,6 +16,7 @@ import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConn
|
||||
import { AzureClientSecretsConnectionForm } from "./AzureClientSecretsConnectionForm";
|
||||
import { AzureDevOpsConnectionForm } from "./AzureDevOpsConnectionForm";
|
||||
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
||||
import { BitbucketConnectionForm } from "./BitbucketConnectionForm";
|
||||
import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
||||
import { CloudflareConnectionForm } from "./CloudflareConnectionForm";
|
||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||
@@ -135,6 +136,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Bitbucket:
|
||||
return <BitbucketConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Zabbix:
|
||||
return <ZabbixConnectionForm onSubmit={onSubmit} />;
|
||||
default:
|
||||
@@ -231,6 +234,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Bitbucket:
|
||||
return <BitbucketConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Zabbix:
|
||||
return <ZabbixConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
default:
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { BitbucketConnectionMethod, TBitbucketConnection } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import {
|
||||
genericAppConnectionFieldsSchema,
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type Props = {
|
||||
appConnection?: TBitbucketConnection;
|
||||
onSubmit: (formData: FormData) => void;
|
||||
};
|
||||
|
||||
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.Bitbucket)
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
rootSchema.extend({
|
||||
method: z.literal(BitbucketConnectionMethod.ApiToken),
|
||||
credentials: z.object({
|
||||
email: z.string().email().trim().min(1, "Email required"),
|
||||
apiToken: z.string().trim().min(1, "API Token required")
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const BitbucketConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.Bitbucket,
|
||||
method: BitbucketConnectionMethod.ApiToken
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.Bitbucket].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Method"
|
||||
>
|
||||
<Select
|
||||
isDisabled={isUpdate}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(BitbucketConnectionMethod).map((method) => {
|
||||
return (
|
||||
<SelectItem value={method} key={method}>
|
||||
{getAppConnectionMethodDetails(method).name}{" "}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.email"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error?.message)} label="Email">
|
||||
<Input {...field} placeholder="[email protected]" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.apiToken"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="API Token"
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Credentials" : "Connect to Bitbucket"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
+13
-13
@@ -20,9 +20,9 @@ import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useCreateIntegration,
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthBitBucketWorkspaces
|
||||
useGetIntegrationAuthBitbucketWorkspaces
|
||||
} from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthBitBucketEnvironments } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { useGetIntegrationAuthBitbucketEnvironments } from "@app/hooks/api/integrationAuth/queries";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
enum BitbucketScope {
|
||||
@@ -89,8 +89,8 @@ export const BitbucketConfigurePage = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const bitBucketWorkspace = watch("targetWorkspace");
|
||||
const bitBucketRepo = watch("targetRepo");
|
||||
const bitbucketWorkspace = watch("targetWorkspace");
|
||||
const bitbucketRepo = watch("targetRepo");
|
||||
|
||||
const integrationAuthId = useSearch({
|
||||
from: ROUTE_PATHS.SecretManager.Integratons.BitbucketConfigurePage.id,
|
||||
@@ -99,21 +99,21 @@ export const BitbucketConfigurePage = () => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: bitbucketWorkspaces, isPending: isBitbucketWorkspacesLoading } =
|
||||
useGetIntegrationAuthBitBucketWorkspaces((integrationAuthId as string) ?? "");
|
||||
useGetIntegrationAuthBitbucketWorkspaces((integrationAuthId as string) ?? "");
|
||||
|
||||
const { data: bitbucketRepos, isPending: isBitbucketReposLoading } = useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
workspaceSlug: bitBucketWorkspace?.slug
|
||||
workspaceSlug: bitbucketWorkspace?.slug
|
||||
});
|
||||
|
||||
const { data: bitbucketEnvironments, isPending: isBitbucketEnvironmentsLoading } =
|
||||
useGetIntegrationAuthBitBucketEnvironments(
|
||||
useGetIntegrationAuthBitbucketEnvironments(
|
||||
{
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
workspaceSlug: bitBucketWorkspace?.slug ?? "",
|
||||
repoSlug: bitBucketRepo?.appId ?? ""
|
||||
workspaceSlug: bitbucketWorkspace?.slug ?? "",
|
||||
repoSlug: bitbucketRepo?.appId ?? ""
|
||||
},
|
||||
{ enabled: Boolean(bitBucketWorkspace?.slug && bitBucketRepo?.appId) }
|
||||
{ enabled: Boolean(bitbucketWorkspace?.slug && bitbucketRepo?.appId) }
|
||||
);
|
||||
|
||||
const onSubmit = async ({
|
||||
@@ -167,8 +167,8 @@ export const BitbucketConfigurePage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
bitBucketWorkspace ||
|
||||
bitBucketRepo ||
|
||||
bitbucketWorkspace ||
|
||||
bitbucketRepo ||
|
||||
!bitbucketRepos ||
|
||||
!bitbucketWorkspaces ||
|
||||
!currentWorkspace
|
||||
@@ -325,7 +325,7 @@ export const BitbucketConfigurePage = () => {
|
||||
? "Select an environment..."
|
||||
: "No environments found..."
|
||||
}
|
||||
isLoading={isBitbucketEnvironmentsLoading && Boolean(bitBucketRepo)}
|
||||
isLoading={isBitbucketEnvironmentsLoading && Boolean(bitbucketRepo)}
|
||||
isDisabled={!bitbucketEnvironments?.length || isBitbucketEnvironmentsLoading}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
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, workspaceSlug }
|
||||
} = dataSource;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Workspace Slug">{workspaceSlug}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Scan Repositories">
|
||||
{includeRepos.includes("*") ? "All" : includeRepos.join(", ")}
|
||||
</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
@@ -3,6 +3,7 @@ import {
|
||||
TSecretScanningDataSource
|
||||
} from "@app/hooks/api/secretScanningV2";
|
||||
|
||||
import { BitbucketDataSourceConfigDisplay } from "./BitbucketDataSourceConfigDisplay";
|
||||
import { GitHubDataSourceConfigDisplay } from "./GitHubDataSourceConfigDisplay";
|
||||
|
||||
type Props = {
|
||||
@@ -13,6 +14,8 @@ export const DataSourceConfigDisplay = ({ dataSource }: Props) => {
|
||||
switch (dataSource.type) {
|
||||
case SecretScanningDataSource.GitHub:
|
||||
return <GitHubDataSourceConfigDisplay dataSource={dataSource} />;
|
||||
case SecretScanningDataSource.Bitbucket:
|
||||
return <BitbucketDataSourceConfigDisplay dataSource={dataSource} />;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled dataSource type ${(dataSource as TSecretScanningDataSource).type}`
|
||||
|
||||
+5
-3
@@ -112,10 +112,12 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
const isActive = includeRepos.includes("*") || includeRepos.includes(name);
|
||||
|
||||
if (filters.status.length === 1) {
|
||||
if (filters.status.includes(ResourceStatus.Active)) {
|
||||
return isActive;
|
||||
if (filters.status.includes(ResourceStatus.Active) && !isActive) {
|
||||
return false;
|
||||
}
|
||||
if (filters.status.includes(ResourceStatus.Inactive) && isActive) {
|
||||
return false;
|
||||
}
|
||||
return !isActive;
|
||||
}
|
||||
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
|
||||
+1
-1
@@ -294,7 +294,7 @@ export const SecretScanningDataSourcesTable = ({ dataSources }: Props) => {
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th className="w-60">Platform</Th>
|
||||
<Th className="min-w-36">Platform</Th>
|
||||
<Th className="w-1/3">
|
||||
<div className="flex items-center">
|
||||
Name
|
||||
|
||||
Reference in New Issue
Block a user