Fleet UI: Consistent URL validation (#9806)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* Update to better URL validation in UI
|
||||
@@ -55,12 +55,12 @@ const enableJiraPoliciesIntegration = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: false,
|
||||
},
|
||||
},
|
||||
@@ -115,12 +115,12 @@ const enableZendeskPoliciesIntegration = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: false,
|
||||
},
|
||||
},
|
||||
@@ -508,11 +508,15 @@ describe("Policies flow (seeded)", () => {
|
||||
cy.findByRole("button", { name: /manage automations/i }).click();
|
||||
});
|
||||
cy.getAttached(".manage-automations-modal").within(() => {
|
||||
// Ensure clicking on slider after modal animation
|
||||
cy.wait(300); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.getAttached(".fleet-slider").click();
|
||||
cy.getAttached(".fleet-checkbox__input").check({ force: true });
|
||||
});
|
||||
cy.getAttached("#webhook-url").click().type("www.foo.com/bar");
|
||||
cy.getAttached("#webhook-url").click().type("http://www.foo.com/bar");
|
||||
cy.findByRole("button", { name: /^Save$/ }).click();
|
||||
// Ensure update
|
||||
cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
// Confirm failing policies webhook was added successfully
|
||||
cy.findByText(/updated policy automations/i).should("exist");
|
||||
cy.getAttached(".button-wrap").within(() => {
|
||||
@@ -523,6 +527,8 @@ describe("Policies flow (seeded)", () => {
|
||||
});
|
||||
// reset slider for subsequent tests
|
||||
cy.getAttached(".manage-automations-modal").within(() => {
|
||||
// Ensure clicking on slider after modal animation
|
||||
cy.wait(300); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.getAttached(".fleet-slider").click();
|
||||
});
|
||||
cy.findByRole("button", { name: /^Save$/ }).click();
|
||||
|
||||
@@ -194,7 +194,7 @@ describe("App settings flow", () => {
|
||||
// specifically targeting this one to avoid conflict
|
||||
// with cypress seeing multiple "metadata url" - one
|
||||
// in a tooltip, the other as the actual label
|
||||
cy.getAttached("[for='metadataURL']")
|
||||
cy.getAttached("[for='metadataUrl']")
|
||||
.click()
|
||||
.type("http://github.com/fleetdm/fleet");
|
||||
|
||||
@@ -223,7 +223,7 @@ describe("App settings flow", () => {
|
||||
"https://http.cat/100"
|
||||
);
|
||||
|
||||
cy.getAttached("#metadataURL").should(
|
||||
cy.getAttached("#metadataUrl").should(
|
||||
"have.value",
|
||||
"http://github.com/fleetdm/fleet"
|
||||
);
|
||||
@@ -280,7 +280,7 @@ describe("App settings flow", () => {
|
||||
);
|
||||
cy.findByText(/single sign-on options/i).click();
|
||||
|
||||
cy.getAttached("#metadataURL").should(
|
||||
cy.getAttached("#metadataUrl").should(
|
||||
"have.value",
|
||||
"http://github.com/fleetdm/fleet"
|
||||
);
|
||||
|
||||
@@ -55,12 +55,12 @@ const enableWebhook = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "http://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: true,
|
||||
},
|
||||
},
|
||||
@@ -115,12 +115,12 @@ const enableJiraSoftwareIntegration = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "http://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: false,
|
||||
},
|
||||
},
|
||||
@@ -175,12 +175,12 @@ const enableZendeskSoftwareIntegration = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "http://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: false,
|
||||
},
|
||||
},
|
||||
@@ -259,7 +259,7 @@ describe("Software", () => {
|
||||
cy.getAttached(".fleet-slider").click();
|
||||
cy.getAttached("#webhook-radio-btn").next().click();
|
||||
});
|
||||
cy.getAttached("#webhook-url").click().type("www.foo.com/bar");
|
||||
cy.getAttached("#webhook-url").click().type("http://www.foo.com/bar");
|
||||
cy.intercept("PATCH", "/api/latest/fleet/config", enableWebhook).as(
|
||||
"createWebhook"
|
||||
);
|
||||
|
||||
@@ -577,13 +577,13 @@ describe("Premium tier - Global Admin user", () => {
|
||||
cy.getAttached("[id=transparency_url")
|
||||
.should("have.value", "https://fleetdm.com/transparency")
|
||||
.clear()
|
||||
.type("example.com/transparency");
|
||||
.type("http://example.com/transparency");
|
||||
cy.findByRole("button", { name: /save/i }).click();
|
||||
cy.findByText(/successfully updated/i).should("exist");
|
||||
cy.visit("settings/organization/fleet-desktop");
|
||||
cy.getAttached("[id=transparency_url").should(
|
||||
"have.value",
|
||||
"example.com/transparency"
|
||||
"http://example.com/transparency"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -422,15 +422,15 @@ Cypress.Commands.add("addUser", (options = {}) => {
|
||||
Cypress.Commands.add("addDockerHost", (team = "") => {
|
||||
const serverPort = new URL(Cypress.config().baseUrl).port;
|
||||
// Get enroll secret
|
||||
let enrollSecretURL = "/api/latest/fleet/spec/enroll_secret";
|
||||
let enrollSecretUrl = "/api/latest/fleet/spec/enroll_secret";
|
||||
if (team === "apples") {
|
||||
enrollSecretURL = "/api/latest/fleet/teams/1/secrets";
|
||||
enrollSecretUrl = "/api/latest/fleet/teams/1/secrets";
|
||||
} else if (team === "oranges") {
|
||||
enrollSecretURL = "/api/latest/fleet/teams/2/secrets";
|
||||
enrollSecretUrl = "/api/latest/fleet/teams/2/secrets";
|
||||
}
|
||||
|
||||
cy.request({
|
||||
url: enrollSecretURL,
|
||||
url: enrollSecretUrl,
|
||||
auth: {
|
||||
bearer: window.localStorage.getItem("FLEET::auth_token"),
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@ const CONFIG_INTEGRATIONS_AUTOMATIONS = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
@@ -224,12 +224,12 @@ const CONFIG_INTEGRATIONS_AUTOMATIONS_DISABLED = {
|
||||
},
|
||||
failing_policies_webhook: {
|
||||
enable_failing_policies_webhook: false,
|
||||
destination_url: "ok.com",
|
||||
destination_url: "https://www.foo.com/bar",
|
||||
policy_ids: [5, 10],
|
||||
host_batch_size: 0,
|
||||
},
|
||||
vulnerabilities_webhook: {
|
||||
destination_url: "www.foo.com/bar",
|
||||
destination_url: "http://www.foo.com/bar",
|
||||
enable_vulnerabilities_webhook: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ class InputField extends Component {
|
||||
hint: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.arrayOf(PropTypes.string),
|
||||
PropTypes.object,
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -91,6 +92,7 @@ class InputField extends Component {
|
||||
blockAutoComplete,
|
||||
value,
|
||||
} = this.props;
|
||||
|
||||
const { onInputChange } = this;
|
||||
const shouldShowPasswordClass = type === "password";
|
||||
const inputClasses = classnames(baseClass, inputClassName, {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
const URL_REGEX_PATTERN = new RegExp(
|
||||
"^(https?:\\/\\/)?" + // protocol
|
||||
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
|
||||
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
|
||||
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
|
||||
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
|
||||
"(\\#[-a-z\\d_]*)?$",
|
||||
"i"
|
||||
); // fragment locator
|
||||
interface IValidUrl {
|
||||
url: string;
|
||||
protocol?: string;
|
||||
}
|
||||
|
||||
export default (url: string): boolean => {
|
||||
return !!URL_REGEX_PATTERN.test(url);
|
||||
export default ({ url, protocol }: IValidUrl): boolean => {
|
||||
try {
|
||||
const newUrl = new URL(url);
|
||||
if (protocol === "http") {
|
||||
return newUrl.protocol === "http:" || newUrl.protocol === "https:";
|
||||
}
|
||||
if (protocol === "https") {
|
||||
return newUrl.protocol === "https:";
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,36 +111,36 @@ export interface IConfigFormData {
|
||||
smtpAuthenticationMethod: string;
|
||||
smtpAuthenticationType: string;
|
||||
domain: string;
|
||||
smtpEnableSSLTLS: boolean;
|
||||
enableStartTLS: boolean;
|
||||
serverURL: string;
|
||||
orgLogoURL: string;
|
||||
smtpEnableSslTls: boolean;
|
||||
enableStartTls: boolean;
|
||||
serverUrl: string;
|
||||
orgLogoUrl: string;
|
||||
orgName: string;
|
||||
smtpPassword: string;
|
||||
smtpPort?: number;
|
||||
smtpSenderAddress: string;
|
||||
smtpServer: string;
|
||||
smtpUsername: string;
|
||||
verifySSLCerts: boolean;
|
||||
entityID: string;
|
||||
issuerURI: string;
|
||||
idpImageURL: string;
|
||||
verifySslCerts: boolean;
|
||||
entityId: string;
|
||||
issuerUri: string;
|
||||
idpImageUrl: string;
|
||||
metadata: string;
|
||||
metadataURL: string;
|
||||
metadataUrl: string;
|
||||
idpName: string;
|
||||
enableSSO: boolean;
|
||||
enableSSOIDPLogin: boolean;
|
||||
enableSMTP: boolean;
|
||||
enableSso: boolean;
|
||||
enableSsoIdpLogin: boolean;
|
||||
enableSmtp: boolean;
|
||||
enableHostExpiry: boolean;
|
||||
hostExpiryWindow: number;
|
||||
disableLiveQuery: boolean;
|
||||
agentOptions: any;
|
||||
enableHostStatusWebhook: boolean;
|
||||
hostStatusWebhookDestinationURL?: string;
|
||||
hostStatusWebhookDestinationUrl?: string;
|
||||
hostStatusWebhookHostPercentage?: number;
|
||||
hostStatusWebhookDaysCount?: number;
|
||||
enableUsageStatistics: boolean;
|
||||
transparency_url: string;
|
||||
transparencyUrl: string;
|
||||
}
|
||||
|
||||
export interface IConfigFeatures {
|
||||
|
||||
@@ -2760,20 +2760,20 @@
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name":"issuer2",
|
||||
"description":"Certificate issuer distinguished name",
|
||||
"type":"text",
|
||||
"hidden":true,
|
||||
"required":false,
|
||||
"index":false
|
||||
"name": "issuer2",
|
||||
"description": "Certificate issuer distinguished name",
|
||||
"type": "text",
|
||||
"hidden": true,
|
||||
"required": false,
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name":"subject2",
|
||||
"description":"Certificate distinguished name",
|
||||
"type":"text",
|
||||
"hidden":true,
|
||||
"required":false,
|
||||
"index":false
|
||||
"name": "subject2",
|
||||
"description": "Certificate distinguished name",
|
||||
"type": "text",
|
||||
"hidden": true,
|
||||
"required": false,
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -8196,12 +8196,12 @@
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name":"original_filename",
|
||||
"description":"(Executable files only) Original filename",
|
||||
"type":"text",
|
||||
"hidden":true,
|
||||
"required":false,
|
||||
"index":false
|
||||
"name": "original_filename",
|
||||
"description": "(Executable files only) Original filename",
|
||||
"type": "text",
|
||||
"hidden": true,
|
||||
"required": false,
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "bsd_flags",
|
||||
@@ -16664,12 +16664,12 @@
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name":"translated",
|
||||
"description":"Indicates whether the process is running under the Rosetta Translation Environment, yes=1, no=0, error=-1.",
|
||||
"type":"integer",
|
||||
"hidden":false,
|
||||
"required":false,
|
||||
"index":false
|
||||
"name": "translated",
|
||||
"description": "Indicates whether the process is running under the Rosetta Translation Environment, yes=1, no=0, error=-1.",
|
||||
"type": "integer",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+5
-3
@@ -12,6 +12,8 @@ import {
|
||||
import Button from "components/buttons/Button";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import Spinner from "components/Spinner";
|
||||
|
||||
const baseClass = "integration-form";
|
||||
@@ -86,8 +88,8 @@ const IntegrationForm = ({
|
||||
const validateForm = () => {
|
||||
let error = null;
|
||||
|
||||
if (url.slice(0, 8) !== "https://") {
|
||||
error = "URL must begin with https://";
|
||||
if (url && !validUrl({ url, protocol: "https" })) {
|
||||
error = `${url} is not a valid HTTPS URL`;
|
||||
}
|
||||
|
||||
setUrlError(error);
|
||||
@@ -226,7 +228,7 @@ const IntegrationForm = ({
|
||||
tooltip={
|
||||
"\
|
||||
To find the Jira project key, head to your project in <br /> \
|
||||
Jira. Your project key is in URL. For example, in <br /> \
|
||||
Jira. Your project key is located in the URL. For example, in <br /> \
|
||||
“jira.example.com/projects/JRAEXAMPLE,” <br /> \
|
||||
“JRAEXAMPLE” is your project key. \
|
||||
"
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
padding-top: 10px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
&--checkbox {
|
||||
|
||||
@@ -24,27 +24,25 @@ const FleetDesktop = ({
|
||||
isUpdatingSettings,
|
||||
}: IAppConfigFormProps): JSX.Element => {
|
||||
const [formData, setFormData] = useState<
|
||||
Pick<IConfigFormData, "transparency_url">
|
||||
Pick<IConfigFormData, "transparencyUrl">
|
||||
>({
|
||||
transparency_url:
|
||||
transparencyUrl:
|
||||
appConfig.fleet_desktop?.transparency_url || DEFAULT_TRANSPARENCY_URL,
|
||||
});
|
||||
|
||||
const [formErrors, setFormErrors] = useState<IAppConfigFormErrors>({});
|
||||
|
||||
const handleInputChange = ({ value }: IFormField) => {
|
||||
setFormData({ transparency_url: value.toString() });
|
||||
setFormData({ transparencyUrl: value.toString() });
|
||||
setFormErrors({});
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const { transparency_url } = formData;
|
||||
const { transparencyUrl } = formData;
|
||||
|
||||
const errors: IAppConfigFormErrors = {};
|
||||
if (!transparency_url) {
|
||||
errors.transparency_url = "Transparency URL name must be present";
|
||||
} else if (!validUrl(transparency_url)) {
|
||||
errors.transparency_url = `${transparency_url} is not a valid URL`;
|
||||
if (transparencyUrl && !validUrl({ url: transparencyUrl })) {
|
||||
errors.transparency_url = `${transparencyUrl} is not a valid URL`;
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
@@ -55,7 +53,7 @@ const FleetDesktop = ({
|
||||
|
||||
const formDataForAPI: Pick<IConfig, "fleet_desktop"> = {
|
||||
fleet_desktop: {
|
||||
transparency_url: formData.transparency_url,
|
||||
transparency_url: formData.transparencyUrl,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -75,10 +73,11 @@ const FleetDesktop = ({
|
||||
label="Custom transparency URL"
|
||||
onChange={handleInputChange}
|
||||
name="transparency_url"
|
||||
value={formData.transparency_url}
|
||||
value={formData.transparencyUrl}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.transparency_url}
|
||||
placeholder="https://fleetdm.com/transparency"
|
||||
/>
|
||||
<p className={`${baseClass}__component-details`}>
|
||||
When an end user clicks “Transparency” in the Fleet Desktop menu, by
|
||||
|
||||
@@ -7,6 +7,8 @@ import Checkbox from "components/forms/fields/Checkbox";
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import {
|
||||
IAppConfigFormProps,
|
||||
@@ -32,7 +34,7 @@ const HostStatusWebhook = ({
|
||||
enableHostStatusWebhook:
|
||||
appConfig.webhook_settings.host_status_webhook
|
||||
.enable_host_status_webhook || false,
|
||||
hostStatusWebhookDestinationURL:
|
||||
hostStatusWebhookDestinationUrl:
|
||||
appConfig.webhook_settings.host_status_webhook.destination_url || "",
|
||||
hostStatusWebhookHostPercentage:
|
||||
appConfig.webhook_settings.host_status_webhook.host_percentage ||
|
||||
@@ -43,7 +45,7 @@ const HostStatusWebhook = ({
|
||||
|
||||
const {
|
||||
enableHostStatusWebhook,
|
||||
hostStatusWebhookDestinationURL,
|
||||
hostStatusWebhookDestinationUrl,
|
||||
hostStatusWebhookHostPercentage,
|
||||
hostStatusWebhookDaysCount,
|
||||
} = formData;
|
||||
@@ -59,8 +61,10 @@ const HostStatusWebhook = ({
|
||||
const errors: IAppConfigFormErrors = {};
|
||||
|
||||
if (enableHostStatusWebhook) {
|
||||
if (!hostStatusWebhookDestinationURL) {
|
||||
if (!hostStatusWebhookDestinationUrl) {
|
||||
errors.destination_url = "Destination URL must be present";
|
||||
} else if (!validUrl({ url: hostStatusWebhookDestinationUrl })) {
|
||||
errors.server_url = `${hostStatusWebhookDestinationUrl} is not a valid URL`;
|
||||
}
|
||||
|
||||
if (!hostStatusWebhookDaysCount) {
|
||||
@@ -92,7 +96,7 @@ const HostStatusWebhook = ({
|
||||
webhook_settings: {
|
||||
host_status_webhook: {
|
||||
enable_host_status_webhook: enableHostStatusWebhook,
|
||||
destination_url: hostStatusWebhookDestinationURL,
|
||||
destination_url: hostStatusWebhookDestinationUrl,
|
||||
host_percentage: hostStatusWebhookHostPercentage,
|
||||
days_count: hostStatusWebhookDaysCount,
|
||||
},
|
||||
@@ -171,8 +175,8 @@ const HostStatusWebhook = ({
|
||||
placeholder="https://server.com/example"
|
||||
label="Destination URL"
|
||||
onChange={handleInputChange}
|
||||
name="hostStatusWebhookDestinationURL"
|
||||
value={hostStatusWebhookDestinationURL}
|
||||
name="hostStatusWebhookDestinationUrl"
|
||||
value={hostStatusWebhookDestinationUrl}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.destination_url}
|
||||
|
||||
@@ -41,7 +41,7 @@ const Info = ({
|
||||
errors.org_name = "Organization name must be present";
|
||||
}
|
||||
|
||||
if (orgLogoURL && !validUrl(orgLogoURL)) {
|
||||
if (orgLogoURL && !validUrl({ url: orgLogoURL, protocol: "http" })) {
|
||||
errors.org_logo_url = `${orgLogoURL} is not a valid URL`;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ import {
|
||||
const baseClass = "app-config-form";
|
||||
|
||||
interface ISsoFormData {
|
||||
enableSSO?: boolean;
|
||||
enableSso?: boolean;
|
||||
idpName?: string;
|
||||
entityID?: string;
|
||||
issuerURI?: string;
|
||||
idpImageURL?: string;
|
||||
entityId?: string;
|
||||
issuerUri?: string;
|
||||
idpImageUrl?: string;
|
||||
metadata?: string;
|
||||
metadataURL?: string;
|
||||
enableSSOIDPLogin?: boolean;
|
||||
enableJITProvisioning?: boolean;
|
||||
metadataUrl?: string;
|
||||
enableSsoIdpLogin?: boolean;
|
||||
enableJitProvisioning?: boolean;
|
||||
}
|
||||
|
||||
const Sso = ({
|
||||
@@ -34,28 +34,28 @@ const Sso = ({
|
||||
isUpdatingSettings,
|
||||
}: IAppConfigFormProps): JSX.Element => {
|
||||
const [formData, setFormData] = useState<ISsoFormData>({
|
||||
enableSSO: appConfig.sso_settings.enable_sso ?? false,
|
||||
enableSso: appConfig.sso_settings.enable_sso ?? false,
|
||||
idpName: appConfig.sso_settings.idp_name ?? "",
|
||||
entityID: appConfig.sso_settings.entity_id ?? "",
|
||||
issuerURI: appConfig.sso_settings.issuer_uri ?? "",
|
||||
idpImageURL: appConfig.sso_settings.idp_image_url ?? "",
|
||||
entityId: appConfig.sso_settings.entity_id ?? "",
|
||||
issuerUri: appConfig.sso_settings.issuer_uri ?? "",
|
||||
idpImageUrl: appConfig.sso_settings.idp_image_url ?? "",
|
||||
metadata: appConfig.sso_settings.metadata ?? "",
|
||||
metadataURL: appConfig.sso_settings.metadata_url ?? "",
|
||||
enableSSOIDPLogin: appConfig.sso_settings.enable_sso_idp_login ?? false,
|
||||
enableJITProvisioning:
|
||||
metadataUrl: appConfig.sso_settings.metadata_url ?? "",
|
||||
enableSsoIdpLogin: appConfig.sso_settings.enable_sso_idp_login ?? false,
|
||||
enableJitProvisioning:
|
||||
appConfig.sso_settings.enable_jit_provisioning ?? false,
|
||||
});
|
||||
|
||||
const {
|
||||
enableSSO,
|
||||
enableSso,
|
||||
idpName,
|
||||
entityID,
|
||||
issuerURI,
|
||||
idpImageURL,
|
||||
entityId,
|
||||
issuerUri,
|
||||
idpImageUrl,
|
||||
metadata,
|
||||
metadataURL,
|
||||
enableSSOIDPLogin,
|
||||
enableJITProvisioning,
|
||||
metadataUrl,
|
||||
enableSsoIdpLogin,
|
||||
enableJitProvisioning,
|
||||
} = formData;
|
||||
|
||||
const [formErrors, setFormErrors] = useState<IAppConfigFormErrors>({});
|
||||
@@ -67,20 +67,25 @@ const Sso = ({
|
||||
const validateForm = () => {
|
||||
const errors: IAppConfigFormErrors = {};
|
||||
|
||||
if (enableSSO) {
|
||||
if (idpImageURL && !validUrl(idpImageURL)) {
|
||||
errors.idp_image_url = `${idpImageURL} is not a valid URL`;
|
||||
if (enableSso) {
|
||||
if (idpImageUrl && !validUrl({ url: idpImageUrl })) {
|
||||
errors.idp_image_url = `${idpImageUrl} is not a valid URL`;
|
||||
}
|
||||
|
||||
if (metadata === "" && metadataURL === "") {
|
||||
errors.metadata_url = "Metadata URL must be present";
|
||||
if (!metadata) {
|
||||
if (!metadataUrl) {
|
||||
errors.metadata_url = "Metadata or Metadata URL must be present";
|
||||
errors.metadata = "Metadata or Metadata URL must be present";
|
||||
} else if (!validUrl({ url: metadataUrl })) {
|
||||
errors.metadata_url = `${metadataUrl} is not a valid URL`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entityID) {
|
||||
if (!entityId) {
|
||||
errors.entity_id = "Entity ID must be present";
|
||||
}
|
||||
|
||||
if (typeof entityID === "string" && entityID.length < 5) {
|
||||
if (typeof entityId === "string" && entityId.length < 5) {
|
||||
errors.entity_id = "Entity ID must be 5 or more characters";
|
||||
}
|
||||
|
||||
@@ -94,7 +99,7 @@ const Sso = ({
|
||||
|
||||
useEffect(() => {
|
||||
validateForm();
|
||||
}, [enableSSO]);
|
||||
}, [idpImageUrl, metadata, metadataUrl, entityId, idpName]);
|
||||
|
||||
const onFormSubmit = (evt: React.MouseEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
@@ -102,15 +107,15 @@ const Sso = ({
|
||||
// Formatting of API not UI
|
||||
const formDataToSubmit = {
|
||||
sso_settings: {
|
||||
entity_id: entityID?.trim(),
|
||||
issuer_uri: issuerURI?.trim(),
|
||||
idp_image_url: idpImageURL?.trim(),
|
||||
entity_id: entityId?.trim(),
|
||||
issuer_uri: issuerUri?.trim(),
|
||||
idp_image_url: idpImageUrl?.trim(),
|
||||
metadata: metadata?.trim(),
|
||||
metadata_url: metadataURL?.trim(),
|
||||
metadata_url: metadataUrl?.trim(),
|
||||
idp_name: idpName?.trim(),
|
||||
enable_sso: enableSSO,
|
||||
enable_sso_idp_login: enableSSOIDPLogin,
|
||||
enable_jit_provisioning: enableJITProvisioning,
|
||||
enable_sso: enableSso,
|
||||
enable_sso_idp_login: enableSsoIdpLogin,
|
||||
enable_jit_provisioning: enableJitProvisioning,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -124,8 +129,8 @@ const Sso = ({
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<Checkbox
|
||||
onChange={handleInputChange}
|
||||
name="enableSSO"
|
||||
value={enableSSO}
|
||||
name="enableSso"
|
||||
value={enableSso}
|
||||
parseTarget
|
||||
>
|
||||
Enable single sign-on
|
||||
@@ -153,8 +158,8 @@ const Sso = ({
|
||||
</span>
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
name="entityID"
|
||||
value={entityID}
|
||||
name="entityId"
|
||||
value={entityId}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.entity_id}
|
||||
@@ -165,8 +170,8 @@ const Sso = ({
|
||||
<InputField
|
||||
label="Issuer URI"
|
||||
onChange={handleInputChange}
|
||||
name="issuerURI"
|
||||
value={issuerURI}
|
||||
name="issuerUri"
|
||||
value={issuerUri}
|
||||
parseTarget
|
||||
tooltip="The issuer URI supplied by the identity provider."
|
||||
/>
|
||||
@@ -175,8 +180,8 @@ const Sso = ({
|
||||
<InputField
|
||||
label="IDP image URL"
|
||||
onChange={handleInputChange}
|
||||
name="idpImageURL"
|
||||
value={idpImageURL}
|
||||
name="idpImageUrl"
|
||||
value={idpImageUrl}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.idp_image_url}
|
||||
@@ -192,6 +197,7 @@ const Sso = ({
|
||||
value={metadata}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.metadata}
|
||||
tooltip="Metadata provided by the identity provider. Either<br/> metadata or a metadata url must be provided."
|
||||
/>
|
||||
</div>
|
||||
@@ -205,8 +211,8 @@ const Sso = ({
|
||||
</span>
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
name="metadataURL"
|
||||
value={metadataURL}
|
||||
name="metadataUrl"
|
||||
value={metadataUrl}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.metadata_url}
|
||||
@@ -216,8 +222,8 @@ const Sso = ({
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<Checkbox
|
||||
onChange={handleInputChange}
|
||||
name="enableSSOIDPLogin"
|
||||
value={enableSSOIDPLogin}
|
||||
name="enableSsoIdpLogin"
|
||||
value={enableSsoIdpLogin}
|
||||
parseTarget
|
||||
>
|
||||
Allow SSO login initiated by identity provider
|
||||
@@ -227,8 +233,8 @@ const Sso = ({
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<Checkbox
|
||||
onChange={handleInputChange}
|
||||
name="enableJITProvisioning"
|
||||
value={enableJITProvisioning}
|
||||
name="enableJitProvisioning"
|
||||
value={enableJitProvisioning}
|
||||
parseTarget
|
||||
>
|
||||
<>
|
||||
|
||||
@@ -3,6 +3,8 @@ import React, { useState } from "react";
|
||||
import Button from "components/buttons/Button";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import {
|
||||
IAppConfigFormProps,
|
||||
IFormField,
|
||||
@@ -31,9 +33,10 @@ const WebAddress = ({
|
||||
|
||||
const validateForm = () => {
|
||||
const errors: IAppConfigFormErrors = {};
|
||||
|
||||
if (!serverURL) {
|
||||
errors.server_url = "Fleet server URL must be present";
|
||||
} else if (!validUrl({ url: serverURL, protocol: "http" })) {
|
||||
errors.server_url = `${serverURL} is not a valid URL`;
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface IFormField {
|
||||
}
|
||||
|
||||
export interface IAppConfigFormErrors {
|
||||
metadata?: string | null;
|
||||
metadata_url?: string | null;
|
||||
entity_id?: string | null;
|
||||
idp_name?: string | null;
|
||||
|
||||
+3
@@ -18,6 +18,7 @@ import Dropdown from "components/forms/fields/Dropdown";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Radio from "components/forms/fields/Radio";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import PreviewPayloadModal from "../PreviewPayloadModal";
|
||||
import PreviewTicketModal from "../PreviewTicketModal";
|
||||
@@ -192,6 +193,8 @@ const ManageAutomationsModal = ({
|
||||
|
||||
if (isWebhookEnabled && !destinationUrl) {
|
||||
newErrors.url = "Please add a destination URL";
|
||||
} else if (!validUrl({ url: destinationUrl })) {
|
||||
newErrors.url = `${destinationUrl} is not a valid URL`;
|
||||
} else {
|
||||
delete newErrors.url;
|
||||
}
|
||||
|
||||
+8
-3
@@ -26,6 +26,7 @@ import Slider from "components/forms/fields/Slider";
|
||||
import Radio from "components/forms/fields/Radio";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook";
|
||||
import useDeepEffect from "hooks/useDeepEffect";
|
||||
@@ -60,8 +61,12 @@ interface IManageAutomationsModalProps {
|
||||
const validateWebhookURL = (url: string) => {
|
||||
const errors: { [key: string]: string } = {};
|
||||
|
||||
if (url === "") {
|
||||
if (!url) {
|
||||
errors.url = "Please add a destination URL";
|
||||
} else if (!validUrl({ url })) {
|
||||
errors.url = `${url} is not a valid URL`;
|
||||
} else {
|
||||
delete errors.url;
|
||||
}
|
||||
|
||||
const valid = !size(errors);
|
||||
@@ -186,7 +191,7 @@ const ManageAutomationsModal = ({
|
||||
const handleSaveAutomation = (evt: React.MouseEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const { valid: validUrl, errors: newErrors } = validateWebhookURL(
|
||||
const { valid: newValidUrl, errors: newErrors } = validateWebhookURL(
|
||||
destinationUrl
|
||||
);
|
||||
setErrors({
|
||||
@@ -232,7 +237,7 @@ const ManageAutomationsModal = ({
|
||||
return;
|
||||
}
|
||||
if (!integrationEnabled) {
|
||||
if (!validUrl) {
|
||||
if (!newValidUrl) {
|
||||
return;
|
||||
}
|
||||
// set enable_vulnerabilities_webhook to true
|
||||
|
||||
Reference in New Issue
Block a user