From 51dafa4c5ef149cb7b3c32b6beb6733d13474db9 Mon Sep 17 00:00:00 2001 From: gaurav2733 Date: Tue, 13 Feb 2024 22:28:19 +0530 Subject: [PATCH 1/5] feat(octa-azure-ingest-form): Add ingestion form to ingestion for Okta and AzureAD --- .../ingest/source/builder/RecipeForm/azure.ts | 175 ++++++++++++++++++ .../source/builder/RecipeForm/constants.ts | 14 +- .../ingest/source/builder/RecipeForm/okta.ts | 162 ++++++++++++++++ 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts create mode 100644 datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts new file mode 100644 index 00000000000000..d019c9f1dde7d0 --- /dev/null +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts @@ -0,0 +1,175 @@ +import { RecipeField, FieldType, setListValuesOnRecipe } from './common'; + +const validateURL = (fieldName) => { + return { + validator(_, value) { + const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); + const isURLValid = URLPattern.test(value); + if (!value || isURLValid) { + return Promise.resolve(); + } + return Promise.reject(new Error(`A valid ${fieldName} is required.`)); + }, + }; +}; + + +export const AZURE_CLIENT_ID: RecipeField = { + name: 'client_id', + label: 'Client ID', + tooltip: 'Your Azure Client ID.', + type: FieldType.TEXT, + fieldPath: 'source.config.client_id', + placeholder: '00000000-0000-0000-0000-000000000000', + required: true, + rules: null, +}; + +export const AZURE_TENANT_ID: RecipeField = { + name: 'tenant_id', + label: 'Tenant ID', + tooltip: 'Your Azure Tenant ID.', + type: FieldType.TEXT, + fieldPath: 'source.config.tenant_id', + placeholder: '00000000-0000-0000-0000-000000000000', + required: true, + rules: null, +}; + +export const AZURE_CLIENT_SECRET: RecipeField = { + name: 'client_secret', + label: 'Client Secret', + tooltip: 'The Azure client secret.', + type: FieldType.SECRET, + fieldPath: 'source.config.client_secret', + placeholder: '${AZURE_AD_CLIENT_SECRET}', + required: true, + rules: null, +}; + +export const AZURE_REDIRECT_URL: RecipeField = { + name: 'redirect', + label: 'Redirect URL', + tooltip: 'Your Redirect URL.', + type: FieldType.TEXT, + fieldPath: 'source.config.redirect', + placeholder: 'https://login.microsoftonline.com/common/oauth2/nativeclient', + required: true, + rules: [() => validateURL('Redirect URL')], +}; + +export const AZURE_AUTHORITY_URL: RecipeField = { + name: 'authority', + label: 'Authority URL', + tooltip: 'Your Authority URL.', + type: FieldType.TEXT, + fieldPath: 'source.config.authority', + placeholder: 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000', + required: true, + rules: [() => validateURL('Azure authority URL')], +}; + +export const AZURE_TOKEN_URL: RecipeField = { + name: 'token_url', + label: 'Token URL', + tooltip: 'Your Token URL.', + type: FieldType.TEXT, + fieldPath: 'source.config.token_url', + placeholder: 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token', + required: true, + rules: [() => validateURL('Azure token URL')], +}; + +export const AZURE_GRAPH_URL: RecipeField = { + name: 'graph_url', + label: 'Graph URL', + tooltip: 'The Graph URL.', + type: FieldType.TEXT, + fieldPath: 'source.config.graph_url', + placeholder: 'https://graph.microsoft.com/v1.0', + required: true, + rules: [() => validateURL('Graph url URL')], +}; + +export const AZURE_INGEST_USERS: RecipeField = { + name: 'ingest_users', + label: 'Ingest Users', + tooltip: 'Flag to determine whether to ingest users from Azure AD or not.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.ingest_users', + rules: null, +}; + +export const AZURE_INGEST_GROUPS: RecipeField = { + name: 'ingest_groups', + label: 'Ingest Groups', + tooltip: 'Flag to determine whether to ingest groups from Azure AD or not.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.ingest_groups', + rules: null, +}; + +const schemaAllowFieldPathGroup = 'source.config.groups_pattern.allow'; +export const GROUP_ALLOW: RecipeField = { + name: 'groups.allow', + label: 'Allow Patterns', + tooltip: + 'Only include specific schemas by providing the name of a schema, or a regular expression (regex) to include specific schemas. If not provided, all schemas inside allowed databases will be included.', + placeholder: 'group_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPathGroup, + rules: null, + section: 'Group', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaAllowFieldPathGroup), +}; + +const schemaDenyFieldPathGroup = 'source.config.groups_pattern.deny'; +export const GROUP_DENY: RecipeField = { + name: 'groups.deny', + label: 'Deny Patterns', + tooltip: + 'Exclude specific schemas by providing the name of a schema, or a regular expression (regex). If not provided, all schemas inside allowed databases will be included. Deny patterns always take precedence over allow patterns.', + placeholder: 'user_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaDenyFieldPathGroup, + rules: null, + section: 'Group', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaDenyFieldPathGroup), +}; + +const schemaAllowFieldPathUser = 'source.config.users_pattern.allow'; +export const USER_ALLOW: RecipeField = { + name: 'user.allow', + label: 'Allow Patterns', + tooltip: + 'Exclude specific schemas by providing the name of a schema, or a regular expression (regex). If not provided, all schemas inside allowed databases will be included. Deny patterns always take precedence over allow patterns.', + placeholder: 'user_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPathUser, + rules: null, + section: 'User', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaAllowFieldPathUser), +}; + + +const schemaDenyFieldPathUser = 'source.config.users_pattern.deny'; +export const USER_DENY: RecipeField = { + name: 'user.deny', + label: 'Deny Patterns', + tooltip: + 'Exclude specific schemas by providing the name of a schema, or a regular expression (regex). If not provided, all schemas inside allowed databases will be included. Deny patterns always take precedence over allow patterns.', + placeholder: 'user_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaDenyFieldPathUser, + rules: null, + section: 'User', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaDenyFieldPathUser), +}; diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts index 844bf50926764a..e53c969470bb75 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts @@ -83,7 +83,7 @@ import { PROJECT_NAME, } from './lookml'; import { PRESTO, PRESTO_HOST_PORT, PRESTO_DATABASE, PRESTO_USERNAME, PRESTO_PASSWORD } from './presto'; -import { BIGQUERY_BETA, CSV, DBT_CLOUD, MYSQL, POWER_BI, UNITY_CATALOG, VERTICA } from '../constants'; +import { AZURE, BIGQUERY_BETA, CSV, DBT_CLOUD, MYSQL, OKTA, POWER_BI, UNITY_CATALOG, VERTICA } from '../constants'; import { BIGQUERY_BETA_PROJECT_ID, DATASET_ALLOW, DATASET_DENY, PROJECT_ALLOW, PROJECT_DENY } from './bigqueryBeta'; import { MYSQL_HOST_PORT, MYSQL_PASSWORD, MYSQL_USERNAME } from './mysql'; import { MSSQL, MSSQL_DATABASE, MSSQL_HOST_PORT, MSSQL_PASSWORD, MSSQL_USERNAME } from './mssql'; @@ -141,6 +141,8 @@ import { INCLUDE_PROJECTIONS_LINEAGE, } from './vertica'; import { CSV_ARRAY_DELIMITER, CSV_DELIMITER, CSV_FILE_URL, CSV_WRITE_SEMANTICS } from './csv'; +import { INCLUDE_DEPROVISIONED_USERS, INCLUDE_SUSPENDED_USERS, INGEST_GROUPS, INGEST_USERS, OKTA_API_TOKEN, OKTA_DOMAIN_URL, POFILE_TO_GROUP, POFILE_TO_GROUP_REGX_ALLOW, POFILE_TO_GROUP_REGX_DENY, POFILE_TO_USER, POFILE_TO_USER_REGX_ALLOW, POFILE_TO_USER_REGX_DENY } from './okta'; +import { AZURE_AUTHORITY_URL, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_GRAPH_URL, AZURE_INGEST_GROUPS, AZURE_INGEST_USERS, AZURE_REDIRECT_URL, AZURE_TENANT_ID, AZURE_TOKEN_URL, GROUP_ALLOW, GROUP_DENY, USER_ALLOW, USER_DENY } from './azure'; export enum RecipeSections { Connection = 0, @@ -459,6 +461,16 @@ export const RECIPE_FIELDS: RecipeFields = { filterFields: [], advancedFields: [CSV_ARRAY_DELIMITER, CSV_DELIMITER, CSV_WRITE_SEMANTICS], }, + [OKTA]: { + fields: [OKTA_DOMAIN_URL,OKTA_API_TOKEN,POFILE_TO_USER,POFILE_TO_GROUP], + filterFields: [POFILE_TO_USER_REGX_ALLOW,POFILE_TO_USER_REGX_DENY,POFILE_TO_GROUP_REGX_ALLOW,POFILE_TO_GROUP_REGX_DENY], + advancedFields: [INGEST_USERS,INGEST_GROUPS,INCLUDE_DEPROVISIONED_USERS,INCLUDE_SUSPENDED_USERS], + }, + [AZURE]: { + fields: [AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET,AZURE_REDIRECT_URL,AZURE_AUTHORITY_URL,AZURE_TOKEN_URL,AZURE_GRAPH_URL], + filterFields: [GROUP_ALLOW,GROUP_DENY,USER_ALLOW,USER_DENY], + advancedFields: [AZURE_INGEST_USERS,AZURE_INGEST_GROUPS], + }, }; export const CONNECTORS_WITH_FORM = new Set(Object.keys(RECIPE_FIELDS)); diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts new file mode 100644 index 00000000000000..ed2e3df3e1acf5 --- /dev/null +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts @@ -0,0 +1,162 @@ +import { RecipeField, FieldType, setListValuesOnRecipe } from './common'; + +const validateURL = (fieldName) => { + return { + validator(_, value) { + const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); + const isURLValid = URLPattern.test(value); + if (!value || isURLValid) { + return Promise.resolve(); + } + return Promise.reject(new Error(`A valid ${fieldName} is required.`)); + }, + }; +}; + +export const OKTA_DOMAIN_URL: RecipeField = { + name: 'okta_domain', + label: 'Okta Domain URL', + tooltip: 'The location of your Okta Domain, without a protocol.', + type: FieldType.TEXT, + fieldPath: 'source.config.okta_domain', + placeholder: 'dev-35531955.okta.com', + required: true, + rules: [() => validateURL('Okta Domain URL')], +}; + +export const OKTA_API_TOKEN: RecipeField = { + name: 'credential.project_id', + label: 'Token', + tooltip: 'An API token generated for the DataHub application inside your Okta Developer Console.', + type: FieldType.SECRET, + fieldPath: 'source.config.okta_api_token', + placeholder: 'd0121d0000882411234e11166c6aaa23ed5d74e0', + rules: null, + required: true, +}; + +export const POFILE_TO_USER: RecipeField = { + name: 'okta_profile_to_username_attr', + label: 'Okta Profile to Username attribute', + tooltip: 'Which Okta User Profile attribute to use as input to DataHub username mapping. Common values used are - login, email.', + type: FieldType.TEXT, + fieldPath: 'source.config.okta_profile_to_username_attr', + placeholder: 'usename', + rules: null, +}; + +export const POFILE_TO_GROUP: RecipeField = { + name: 'okta_profile_to_group_name_attr', + label: 'Okta Profile to group name attribute', + tooltip: 'Which Okta Group Profile attribute to use as input to DataHub group name mapping.', + type: FieldType.TEXT, + fieldPath: 'source.config.okta_profile_to_group_name_attr', + placeholder: 'Group name', + rules: null, +}; + + +const schemaAllowFieldPath = 'source.config.okta_profile_to_username_attr_regex.allow'; +export const POFILE_TO_USER_REGX_ALLOW: RecipeField = { + name: 'user.allow', + label: 'Allow Patterns', + tooltip: + 'Only include specific schemas by providing the name of a schema, or a regular expression (regex) to include specific schemas. If not provided, all schemas inside allowed databases will be included.', + placeholder: 'user_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPath, + rules: null, + section: 'Profile To User', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaAllowFieldPath), +}; + + +const schemaDenyFieldPath = 'source.config.okta_profile_to_username_attr_regex.deny'; +export const POFILE_TO_USER_REGX_DENY: RecipeField = { + name: 'user.deny', + label: 'Deny Patterns', + tooltip: + 'Only include specific schemas by providing the name of a schema, or a regular expression (regex) to include specific schemas. If not provided, all schemas inside allowed databases will be included.', + placeholder: 'user_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPath, + rules: null, + section: 'Profile To User', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaDenyFieldPath), +}; + + +const schemaAllowFieldPathForGroup = 'source.config.okta_profile_to_group_name_regex.allow'; +export const POFILE_TO_GROUP_REGX_ALLOW: RecipeField = { + name: 'group.allow', + label: 'Allow Patterns', + tooltip: + 'Only include specific schemas by providing the name of a schema, or a regular expression (regex) to include specific schemas. If not provided, all schemas inside allowed databases will be included.', + placeholder: 'group_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPathForGroup, + rules: null, + section: 'Profile To Group', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaAllowFieldPathForGroup), +}; + +const schemaDenyFieldPathForGroup = 'source.config.okta_profile_to_group_name_regex.deny'; +export const POFILE_TO_GROUP_REGX_DENY: RecipeField = { + name: 'group.deny', + label: 'Deny Patterns', + tooltip: + 'Only include specific schemas by providing the name of a schema, or a regular expression (regex) to include specific schemas. If not provided, all schemas inside allowed databases will be included.', + placeholder: 'group_pattern', + type: FieldType.LIST, + buttonLabel: 'Add pattern', + fieldPath: schemaAllowFieldPathForGroup, + rules: null, + section: 'Profile To Group', + setValueOnRecipeOverride: (recipe: any, values: string[]) => + setListValuesOnRecipe(recipe, values, schemaDenyFieldPathForGroup), +}; + + + +export const INGEST_USERS: RecipeField = { + name: 'ingest_users', + label: 'Ingest Users', + tooltip: 'Whether users should be ingested into DataHub.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.ingest_users', + rules: null, +}; + +export const INGEST_GROUPS: RecipeField = { + name: 'ingest_groups', + label: 'Ingest Groups', + tooltip: 'Whether groups should be ingested into DataHub.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.ingest_groups', + rules: null, +}; + + +export const INCLUDE_DEPROVISIONED_USERS: RecipeField = { + name: 'include_deprovisioned_users', + label: 'Include deprovisioned users', + tooltip: 'Whether to ingest users in the DEPROVISIONED state from Okta.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.include_deprovisioned_users', + rules: null, +}; +export const INCLUDE_SUSPENDED_USERS: RecipeField = { + name: 'include_suspended_users', + label: 'Include suspended users', + tooltip: 'Whether to ingest users in the SUSPENDED state from Okta.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.include_suspended_users', + rules: null, +}; + From 34fbaee36e744eb67fa62bf455e18d2466614cf8 Mon Sep 17 00:00:00 2001 From: gaurav2733 Date: Wed, 14 Feb 2024 18:13:36 +0530 Subject: [PATCH 2/5] updated tooltips for fields --- .../app/ingest/source/builder/RecipeForm/azure.ts | 14 +++++++------- .../app/ingest/source/builder/RecipeForm/okta.ts | 12 ++++++------ .../src/app/ingest/source/builder/sources.json | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts index d019c9f1dde7d0..48f2286f38450e 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts @@ -17,7 +17,7 @@ const validateURL = (fieldName) => { export const AZURE_CLIENT_ID: RecipeField = { name: 'client_id', label: 'Client ID', - tooltip: 'Your Azure Client ID.', + tooltip: 'Application ID. Found in your app registration on Azure AD Portal', type: FieldType.TEXT, fieldPath: 'source.config.client_id', placeholder: '00000000-0000-0000-0000-000000000000', @@ -28,7 +28,7 @@ export const AZURE_CLIENT_ID: RecipeField = { export const AZURE_TENANT_ID: RecipeField = { name: 'tenant_id', label: 'Tenant ID', - tooltip: 'Your Azure Tenant ID.', + tooltip: 'Directory ID. Found in your app registration on Azure AD Portal', type: FieldType.TEXT, fieldPath: 'source.config.tenant_id', placeholder: '00000000-0000-0000-0000-000000000000', @@ -50,18 +50,18 @@ export const AZURE_CLIENT_SECRET: RecipeField = { export const AZURE_REDIRECT_URL: RecipeField = { name: 'redirect', label: 'Redirect URL', - tooltip: 'Your Redirect URL.', + tooltip: 'Redirect URL. Found in your app registration on Azure AD Portal.', type: FieldType.TEXT, fieldPath: 'source.config.redirect', placeholder: 'https://login.microsoftonline.com/common/oauth2/nativeclient', required: true, - rules: [() => validateURL('Redirect URL')], + rules: [() => validateURL('Redirect URI')], }; export const AZURE_AUTHORITY_URL: RecipeField = { name: 'authority', label: 'Authority URL', - tooltip: 'Your Authority URL.', + tooltip: 'Is a URL that indicates a directory that MSAL can request tokens from..', type: FieldType.TEXT, fieldPath: 'source.config.authority', placeholder: 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000', @@ -72,7 +72,7 @@ export const AZURE_AUTHORITY_URL: RecipeField = { export const AZURE_TOKEN_URL: RecipeField = { name: 'token_url', label: 'Token URL', - tooltip: 'Your Token URL.', + tooltip: 'The token URL that acquires a token from Azure AD for authorizing requests. This source will only work with v1.0 endpoint.', type: FieldType.TEXT, fieldPath: 'source.config.token_url', placeholder: 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token', @@ -83,7 +83,7 @@ export const AZURE_TOKEN_URL: RecipeField = { export const AZURE_GRAPH_URL: RecipeField = { name: 'graph_url', label: 'Graph URL', - tooltip: 'The Graph URL.', + tooltip: 'Microsoft Graph API endpoint', type: FieldType.TEXT, fieldPath: 'source.config.graph_url', placeholder: 'https://graph.microsoft.com/v1.0', diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts index ed2e3df3e1acf5..daf794e9d090a8 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts @@ -67,7 +67,7 @@ export const POFILE_TO_USER_REGX_ALLOW: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaAllowFieldPath, rules: null, - section: 'Profile To User', + section: 'Profile To User Attribute', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaAllowFieldPath), }; @@ -82,9 +82,9 @@ export const POFILE_TO_USER_REGX_DENY: RecipeField = { placeholder: 'user_pattern', type: FieldType.LIST, buttonLabel: 'Add pattern', - fieldPath: schemaAllowFieldPath, + fieldPath: schemaDenyFieldPath, rules: null, - section: 'Profile To User', + section: 'Profile To User Attribute', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaDenyFieldPath), }; @@ -101,7 +101,7 @@ export const POFILE_TO_GROUP_REGX_ALLOW: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaAllowFieldPathForGroup, rules: null, - section: 'Profile To Group', + section: 'Profile To Group Attribute', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaAllowFieldPathForGroup), }; @@ -115,9 +115,9 @@ export const POFILE_TO_GROUP_REGX_DENY: RecipeField = { placeholder: 'group_pattern', type: FieldType.LIST, buttonLabel: 'Add pattern', - fieldPath: schemaAllowFieldPathForGroup, + fieldPath: schemaDenyFieldPathForGroup, rules: null, - section: 'Profile To Group', + section: 'Profile To Group Attribute', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaDenyFieldPathForGroup), }; diff --git a/datahub-web-react/src/app/ingest/source/builder/sources.json b/datahub-web-react/src/app/ingest/source/builder/sources.json index e33de13c100b7f..65ae1cb53d16f2 100644 --- a/datahub-web-react/src/app/ingest/source/builder/sources.json +++ b/datahub-web-react/src/app/ingest/source/builder/sources.json @@ -200,14 +200,14 @@ "name": "azure-ad", "displayName": "Azure AD", "docsUrl": "https://datahubproject.io/docs/generated/ingestion/sources/azure-ad/", - "recipe": "source:\n type: azure-ad\n config:\n client_id: # Your Azure Client ID, e.g. \"00000000-0000-0000-0000-000000000000\"\n tenant_id: # Your Azure Tenant ID, e.g. \"00000000-0000-0000-0000-000000000000\"\n # Add secret in Secrets Tab with this name\n client_secret: \"${AZURE_AD_CLIENT_SECRET}\"\n redirect: # Your Redirect URL, e.g. \"https://login.microsoftonline.com/common/oauth2/nativeclient\"\n authority: # Your Authority URL, e.g. \"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000\"\n token_url: # Your Token URL, e.g. \"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token\"\n graph_url: # The Graph URL, e.g. \"https://graph.microsoft.com/v1.0\"\n \n # Optional flags to ingest users, groups, or both\n ingest_users: True\n ingest_groups: True\n \n # Optional Allow / Deny extraction of particular Groups\n # groups_pattern:\n # allow:\n # - \".*\"\n\n # Optional Allow / Deny extraction of particular Users.\n # users_pattern:\n # allow:\n # - \".*\"" + "recipe": "source:\n type: azure-ad\n config:\n client_id: # Your Azure Client ID, e.g. \"00000000-0000-0000-0000-000000000000\"\n tenant_id: # Your Azure Tenant ID, e.g. \"00000000-0000-0000-0000-000000000000\"\n # Add secret in Secrets Tab with this name\n client_secret: \n redirect: # Your Redirect URL, e.g. \"https://login.microsoftonline.com/common/oauth2/nativeclient\"\n authority: # Your Authority URL, e.g. \"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000\"\n token_url: # Your Token URL, e.g. \"https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token\"\n graph_url: # The Graph URL, e.g. \"https://graph.microsoft.com/v1.0\"\n \n # Optional flags to ingest users, groups, or both\n ingest_users: True\n ingest_groups: True\n \n # Optional Allow / Deny extraction of particular Groups\n # groups_pattern:\n # allow:\n # - \".*\"\n\n # Optional Allow / Deny extraction of particular Users.\n # users_pattern:\n # allow:\n # - \".*\"" }, { "urn": "urn:li:dataPlatform:okta", "name": "okta", "displayName": "Okta", "docsUrl": "https://datahubproject.io/docs/generated/ingestion/sources/okta/", - "recipe": "source:\n type: okta\n config:\n # Coordinates\n okta_domain: # Your Okta Domain, e.g. \"dev-35531955.okta.com\"\n\n # Credentials\n # Add secret in Secrets Tab with relevant names for each variable\n okta_api_token: \"${OKTA_API_TOKEN}\" # Your Okta API Token, e.g. \"11be4R_M2MzDqXawbTHfKGpKee0kuEOfX1RCQSRx99\"\n\n # Optional flags to ingest users, groups, or both\n ingest_users: True\n ingest_groups: True\n\n # Optional: Customize the mapping to DataHub Username from an attribute appearing in the Okta User\n # profile. Reference: https://developer.okta.com/docs/reference/api/users/\n # okta_profile_to_username_attr: str = \"login\"\n # okta_profile_to_username_regex: str = \"([^@]+)\"\n \n # Optional: Customize the mapping to DataHub Group from an attribute appearing in the Okta Group\n # profile. Reference: https://developer.okta.com/docs/reference/api/groups/\n # okta_profile_to_group_name_attr: str = \"name\"\n # okta_profile_to_group_name_regex: str = \"(.*)\"\n \n # Optional: Include deprovisioned or suspended Okta users in the ingestion.\n # include_deprovisioned_users = False\n # include_suspended_users = False" + "recipe": "source:\n type: okta\n config:\n # Coordinates\n okta_domain: # Your Okta Domain, e.g. \"dev-35531955.okta.com\"\n\n # Credentials\n # Add secret in Secrets Tab with relevant names for each variable\n okta_api_token: # Your Okta API Token, e.g. \"11be4R_M2MzDqXawbTHfKGpKee0kuEOfX1RCQSRx99\"\n\n # Optional flags to ingest users, groups, or both\n ingest_users: True\n ingest_groups: True\n\n # Optional: Customize the mapping to DataHub Username from an attribute appearing in the Okta User\n # profile. Reference: https://developer.okta.com/docs/reference/api/users/\n # okta_profile_to_username_attr: str = \"login\"\n # okta_profile_to_username_regex: str = \"([^@]+)\"\n \n # Optional: Customize the mapping to DataHub Group from an attribute appearing in the Okta Group\n # profile. Reference: https://developer.okta.com/docs/reference/api/groups/\n # okta_profile_to_group_name_attr: str = \"name\"\n # okta_profile_to_group_name_regex: str = \"(.*)\"\n \n # Optional: Include deprovisioned or suspended Okta users in the ingestion.\n # include_deprovisioned_users = False\n # include_suspended_users = False" }, { "urn": "urn:li:dataPlatform:vertica", From c1c8b4a79fc888b806182c3ae5a6e17a6f9ef73d Mon Sep 17 00:00:00 2001 From: gaurav2733 Date: Wed, 14 Feb 2024 18:33:36 +0530 Subject: [PATCH 3/5] resolve lint issue and update placeholder --- .../src/app/ingest/source/builder/RecipeForm/azure.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts index 48f2286f38450e..5553671684f774 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts @@ -13,7 +13,6 @@ const validateURL = (fieldName) => { }; }; - export const AZURE_CLIENT_ID: RecipeField = { name: 'client_id', label: 'Client ID', @@ -42,7 +41,7 @@ export const AZURE_CLIENT_SECRET: RecipeField = { tooltip: 'The Azure client secret.', type: FieldType.SECRET, fieldPath: 'source.config.client_secret', - placeholder: '${AZURE_AD_CLIENT_SECRET}', + placeholder: '00000000-0000-0000-0000-000000000000', required: true, rules: null, }; @@ -72,7 +71,8 @@ export const AZURE_AUTHORITY_URL: RecipeField = { export const AZURE_TOKEN_URL: RecipeField = { name: 'token_url', label: 'Token URL', - tooltip: 'The token URL that acquires a token from Azure AD for authorizing requests. This source will only work with v1.0 endpoint.', + tooltip: + 'The token URL that acquires a token from Azure AD for authorizing requests. This source will only work with v1.0 endpoint.', type: FieldType.TEXT, fieldPath: 'source.config.token_url', placeholder: 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token', @@ -157,7 +157,6 @@ export const USER_ALLOW: RecipeField = { setListValuesOnRecipe(recipe, values, schemaAllowFieldPathUser), }; - const schemaDenyFieldPathUser = 'source.config.users_pattern.deny'; export const USER_DENY: RecipeField = { name: 'user.deny', From 2236145bc67c6dc7d1e1774e56ba5f71f9df4e44 Mon Sep 17 00:00:00 2001 From: gaurav2733 Date: Thu, 15 Feb 2024 13:24:37 +0530 Subject: [PATCH 4/5] move validateUrl utility function to utils and added unit test for utility function --- .../RecipeForm/__tests__/utils.test.ts | 26 +++++++++++++++++++ .../ingest/source/builder/RecipeForm/azure.ts | 14 +--------- .../ingest/source/builder/RecipeForm/csv.ts | 14 +--------- .../ingest/source/builder/RecipeForm/okta.ts | 14 +--------- .../src/app/ingest/source/utils.ts | 14 ++++++++++ 5 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 datahub-web-react/src/app/ingest/source/builder/RecipeForm/__tests__/utils.test.ts diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/__tests__/utils.test.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/__tests__/utils.test.ts new file mode 100644 index 00000000000000..160074d94abac4 --- /dev/null +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/__tests__/utils.test.ts @@ -0,0 +1,26 @@ +import { validateURL } from '../../../utils'; + +describe('validateURL function', () => { + it('should resolve if the URL is valid', async () => { + const validator = validateURL('test'); + await expect(validator.validator(null, 'https://example.com')).resolves.toBeUndefined(); + await expect(validator.validator(null, 'http://example.com')).resolves.toBeUndefined(); + await expect(validator.validator(null, 'http://subdomain.example.com/path')).resolves.toBeUndefined(); + }); + + it('should reject if the URL is invalid', async () => { + const validator = validateURL('test url'); + await expect(validator.validator(null, 'http://example')).rejects.toThrowError('A valid test url is required.'); + await expect(validator.validator(null, 'example')).rejects.toThrowError('A valid test url is required.'); + await expect(validator.validator(null, 'http://example')).rejects.toThrowError( + 'A valid test url is required.', + ); + }); + + it('should resolve if the value is empty', async () => { + const validator = validateURL('test'); + await expect(validator.validator(null, '')).resolves.toBeUndefined(); + await expect(validator.validator(null, undefined)).resolves.toBeUndefined(); + await expect(validator.validator(null, null)).resolves.toBeUndefined(); + }); +}); diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts index 5553671684f774..9314be65925eec 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts @@ -1,18 +1,6 @@ +import { validateURL } from '../../utils'; import { RecipeField, FieldType, setListValuesOnRecipe } from './common'; -const validateURL = (fieldName) => { - return { - validator(_, value) { - const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); - const isURLValid = URLPattern.test(value); - if (!value || isURLValid) { - return Promise.resolve(); - } - return Promise.reject(new Error(`A valid ${fieldName} is required.`)); - }, - }; -}; - export const AZURE_CLIENT_ID: RecipeField = { name: 'client_id', label: 'Client ID', diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/csv.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/csv.ts index fba4f3b9d01641..2cb3e7edc94d70 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/csv.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/csv.ts @@ -1,18 +1,6 @@ +import { validateURL } from '../../utils'; import { RecipeField, FieldType } from './common'; -const validateURL = (fieldName) => { - return { - validator(_, value) { - const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); - const isURLValid = URLPattern.test(value); - if (!value || isURLValid) { - return Promise.resolve(); - } - return Promise.reject(new Error(`A valid ${fieldName} is required.`)); - }, - }; -}; - export const CSV_FILE_URL: RecipeField = { name: 'filename', label: 'File URL', diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts index daf794e9d090a8..23352df8c85abf 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts @@ -1,18 +1,6 @@ +import { validateURL } from '../../utils'; import { RecipeField, FieldType, setListValuesOnRecipe } from './common'; -const validateURL = (fieldName) => { - return { - validator(_, value) { - const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); - const isURLValid = URLPattern.test(value); - if (!value || isURLValid) { - return Promise.resolve(); - } - return Promise.reject(new Error(`A valid ${fieldName} is required.`)); - }, - }; -}; - export const OKTA_DOMAIN_URL: RecipeField = { name: 'okta_domain', label: 'Okta Domain URL', diff --git a/datahub-web-react/src/app/ingest/source/utils.ts b/datahub-web-react/src/app/ingest/source/utils.ts index f789ed8434721d..e55d5a598d3d94 100644 --- a/datahub-web-react/src/app/ingest/source/utils.ts +++ b/datahub-web-react/src/app/ingest/source/utils.ts @@ -129,6 +129,20 @@ export const getExecutionRequestStatusDisplayColor = (status: string) => { ); }; +export const validateURL = (fieldName: string) => { + return { + validator(_, value) { + const URLPattern = new RegExp(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=.]+$/); + const isURLValid = URLPattern.test(value); + if (!value || isURLValid) { + return Promise.resolve(); + } + return Promise.reject(new Error(`A valid ${fieldName} is required.`)); + }, + }; +}; + + const ENTITIES_WITH_SUBTYPES = new Set([ EntityType.Dataset.toLowerCase(), EntityType.Container.toLowerCase(), From f763396628e6356574588c6b72f4e08c003d714b Mon Sep 17 00:00:00 2001 From: gaurav2733 Date: Tue, 19 Mar 2024 18:32:49 +0530 Subject: [PATCH 5/5] add fields in advance section --- .../ingest/source/builder/RecipeForm/azure.ts | 9 +++ .../source/builder/RecipeForm/constants.ts | 64 ++++++++++++++++--- .../ingest/source/builder/RecipeForm/okta.ts | 37 ++++++----- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts index 9314be65925eec..9dfbaaae0a26dd 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/azure.ts @@ -160,3 +160,12 @@ export const USER_DENY: RecipeField = { setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaDenyFieldPathUser), }; + +export const SKIP_USERS_WITHOUT_GROUP: RecipeField = { + name: 'skip_users_without_a_group', + label: 'Skip users without group', + tooltip: 'Whether to skip users without group from Okta.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.skip_users_without_a_group', + rules: null, +}; diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts index e53c969470bb75..6a5e6c9de2b96b 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/constants.ts @@ -141,8 +141,36 @@ import { INCLUDE_PROJECTIONS_LINEAGE, } from './vertica'; import { CSV_ARRAY_DELIMITER, CSV_DELIMITER, CSV_FILE_URL, CSV_WRITE_SEMANTICS } from './csv'; -import { INCLUDE_DEPROVISIONED_USERS, INCLUDE_SUSPENDED_USERS, INGEST_GROUPS, INGEST_USERS, OKTA_API_TOKEN, OKTA_DOMAIN_URL, POFILE_TO_GROUP, POFILE_TO_GROUP_REGX_ALLOW, POFILE_TO_GROUP_REGX_DENY, POFILE_TO_USER, POFILE_TO_USER_REGX_ALLOW, POFILE_TO_USER_REGX_DENY } from './okta'; -import { AZURE_AUTHORITY_URL, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_GRAPH_URL, AZURE_INGEST_GROUPS, AZURE_INGEST_USERS, AZURE_REDIRECT_URL, AZURE_TENANT_ID, AZURE_TOKEN_URL, GROUP_ALLOW, GROUP_DENY, USER_ALLOW, USER_DENY } from './azure'; +import { + INCLUDE_DEPROVISIONED_USERS, + INCLUDE_SUSPENDED_USERS, + INGEST_GROUPS, + INGEST_USERS, + OKTA_API_TOKEN, + OKTA_DOMAIN_URL, + POFILE_TO_GROUP, + POFILE_TO_GROUP_REGX_ALLOW, + POFILE_TO_GROUP_REGX_DENY, + POFILE_TO_USER, + POFILE_TO_USER_REGX_ALLOW, + POFILE_TO_USER_REGX_DENY, + SKIP_USERS_WITHOUT_GROUP, +} from './okta'; +import { + AZURE_AUTHORITY_URL, + AZURE_CLIENT_ID, + AZURE_CLIENT_SECRET, + AZURE_GRAPH_URL, + AZURE_INGEST_GROUPS, + AZURE_INGEST_USERS, + AZURE_REDIRECT_URL, + AZURE_TENANT_ID, + AZURE_TOKEN_URL, + GROUP_ALLOW, + GROUP_DENY, + USER_ALLOW, + USER_DENY, +} from './azure'; export enum RecipeSections { Connection = 0, @@ -462,14 +490,34 @@ export const RECIPE_FIELDS: RecipeFields = { advancedFields: [CSV_ARRAY_DELIMITER, CSV_DELIMITER, CSV_WRITE_SEMANTICS], }, [OKTA]: { - fields: [OKTA_DOMAIN_URL,OKTA_API_TOKEN,POFILE_TO_USER,POFILE_TO_GROUP], - filterFields: [POFILE_TO_USER_REGX_ALLOW,POFILE_TO_USER_REGX_DENY,POFILE_TO_GROUP_REGX_ALLOW,POFILE_TO_GROUP_REGX_DENY], - advancedFields: [INGEST_USERS,INGEST_GROUPS,INCLUDE_DEPROVISIONED_USERS,INCLUDE_SUSPENDED_USERS], + fields: [OKTA_DOMAIN_URL, OKTA_API_TOKEN, POFILE_TO_USER, POFILE_TO_GROUP], + filterFields: [ + POFILE_TO_USER_REGX_ALLOW, + POFILE_TO_USER_REGX_DENY, + POFILE_TO_GROUP_REGX_ALLOW, + POFILE_TO_GROUP_REGX_DENY, + ], + advancedFields: [ + INGEST_USERS, + INGEST_GROUPS, + INCLUDE_DEPROVISIONED_USERS, + INCLUDE_SUSPENDED_USERS, + STATEFUL_INGESTION_ENABLED, + SKIP_USERS_WITHOUT_GROUP, + ], }, [AZURE]: { - fields: [AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET,AZURE_REDIRECT_URL,AZURE_AUTHORITY_URL,AZURE_TOKEN_URL,AZURE_GRAPH_URL], - filterFields: [GROUP_ALLOW,GROUP_DENY,USER_ALLOW,USER_DENY], - advancedFields: [AZURE_INGEST_USERS,AZURE_INGEST_GROUPS], + fields: [ + AZURE_CLIENT_ID, + AZURE_TENANT_ID, + AZURE_CLIENT_SECRET, + AZURE_REDIRECT_URL, + AZURE_AUTHORITY_URL, + AZURE_TOKEN_URL, + AZURE_GRAPH_URL, + ], + filterFields: [GROUP_ALLOW, GROUP_DENY, USER_ALLOW, USER_DENY], + advancedFields: [AZURE_INGEST_USERS, AZURE_INGEST_GROUPS, STATEFUL_INGESTION_ENABLED, SKIP_USERS_WITHOUT_GROUP], }, }; diff --git a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts index 23352df8c85abf..6efee3769f908d 100644 --- a/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts +++ b/datahub-web-react/src/app/ingest/source/builder/RecipeForm/okta.ts @@ -24,12 +24,13 @@ export const OKTA_API_TOKEN: RecipeField = { }; export const POFILE_TO_USER: RecipeField = { - name: 'okta_profile_to_username_attr', - label: 'Okta Profile to Username attribute', - tooltip: 'Which Okta User Profile attribute to use as input to DataHub username mapping. Common values used are - login, email.', + name: 'email', + label: 'Okta Email', + tooltip: + 'Which Okta User Profile attribute to use as input to DataHub username mapping. Common values used are - login, email.', type: FieldType.TEXT, fieldPath: 'source.config.okta_profile_to_username_attr', - placeholder: 'usename', + placeholder: 'email', rules: null, }; @@ -43,8 +44,7 @@ export const POFILE_TO_GROUP: RecipeField = { rules: null, }; - -const schemaAllowFieldPath = 'source.config.okta_profile_to_username_attr_regex.allow'; +const schemaAllowFieldPath = 'source.config.okta_profile_to_username_regex.allow'; export const POFILE_TO_USER_REGX_ALLOW: RecipeField = { name: 'user.allow', label: 'Allow Patterns', @@ -55,13 +55,12 @@ export const POFILE_TO_USER_REGX_ALLOW: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaAllowFieldPath, rules: null, - section: 'Profile To User Attribute', + section: 'Okta Profile To User Attribute Regex', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaAllowFieldPath), }; - -const schemaDenyFieldPath = 'source.config.okta_profile_to_username_attr_regex.deny'; +const schemaDenyFieldPath = 'source.config.okta_profile_to_username_regex.deny'; export const POFILE_TO_USER_REGX_DENY: RecipeField = { name: 'user.deny', label: 'Deny Patterns', @@ -72,12 +71,11 @@ export const POFILE_TO_USER_REGX_DENY: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaDenyFieldPath, rules: null, - section: 'Profile To User Attribute', + section: 'Okta Profile To User Attribute Regex', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaDenyFieldPath), }; - const schemaAllowFieldPathForGroup = 'source.config.okta_profile_to_group_name_regex.allow'; export const POFILE_TO_GROUP_REGX_ALLOW: RecipeField = { name: 'group.allow', @@ -89,7 +87,7 @@ export const POFILE_TO_GROUP_REGX_ALLOW: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaAllowFieldPathForGroup, rules: null, - section: 'Profile To Group Attribute', + section: 'Okta Profile To Group Attribute Regex', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaAllowFieldPathForGroup), }; @@ -105,13 +103,10 @@ export const POFILE_TO_GROUP_REGX_DENY: RecipeField = { buttonLabel: 'Add pattern', fieldPath: schemaDenyFieldPathForGroup, rules: null, - section: 'Profile To Group Attribute', + section: 'Okta Profile To Group Attribute Regex', setValueOnRecipeOverride: (recipe: any, values: string[]) => setListValuesOnRecipe(recipe, values, schemaDenyFieldPathForGroup), }; - - - export const INGEST_USERS: RecipeField = { name: 'ingest_users', label: 'Ingest Users', @@ -130,7 +125,6 @@ export const INGEST_GROUPS: RecipeField = { rules: null, }; - export const INCLUDE_DEPROVISIONED_USERS: RecipeField = { name: 'include_deprovisioned_users', label: 'Include deprovisioned users', @@ -148,3 +142,12 @@ export const INCLUDE_SUSPENDED_USERS: RecipeField = { rules: null, }; +export const SKIP_USERS_WITHOUT_GROUP: RecipeField = { + name: 'skip_users_without_a_group', + label: 'Skip users without group', + tooltip: 'Whether to skip users without group from Okta.', + type: FieldType.BOOLEAN, + fieldPath: 'source.config.skip_users_without_a_group', + rules: null, +}; +