From e9e3d23b9d34ce073abe358edf8e73781d0284ce Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 12 Dec 2024 12:43:11 -0300 Subject: [PATCH] [Components] sailpoint #13241 Sources - New Identity (Instant) - New Identity Attribute Change (Instant) Actions - Upload Account Source File - List Certification Campaigns - Submit Access Request --- components/sailpoint/.gitignore | 3 - .../list-certification-campaigns.mjs | 28 ++- .../submit-access-request.mjs | 37 ++-- .../upload-account-source-file.mjs | 29 ++- components/sailpoint/app/sailpoint.app.ts | 13 -- components/sailpoint/common/constants.mjs | 20 ++ components/sailpoint/common/utils.mjs | 8 + components/sailpoint/package.json | 10 +- components/sailpoint/sailpoint.app.mjs | 187 ++++++++++-------- components/sailpoint/sources/common/base.mjs | 74 +++++++ .../identity-attribute-change-instant.mjs | 100 ++-------- .../test-event.mjs | 19 ++ .../new-identity-instant.mjs | 97 ++------- .../new-identity-instant/test-event.mjs | 10 + 14 files changed, 334 insertions(+), 301 deletions(-) delete mode 100644 components/sailpoint/.gitignore delete mode 100644 components/sailpoint/app/sailpoint.app.ts create mode 100644 components/sailpoint/common/constants.mjs create mode 100644 components/sailpoint/common/utils.mjs create mode 100644 components/sailpoint/sources/common/base.mjs create mode 100644 components/sailpoint/sources/identity-attribute-change-instant/test-event.mjs create mode 100644 components/sailpoint/sources/new-identity-instant/test-event.mjs diff --git a/components/sailpoint/.gitignore b/components/sailpoint/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/sailpoint/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/sailpoint/actions/list-certification-campaigns/list-certification-campaigns.mjs b/components/sailpoint/actions/list-certification-campaigns/list-certification-campaigns.mjs index 081ef16aa97a9..da477e9f98af0 100644 --- a/components/sailpoint/actions/list-certification-campaigns/list-certification-campaigns.mjs +++ b/components/sailpoint/actions/list-certification-campaigns/list-certification-campaigns.mjs @@ -1,17 +1,13 @@ import sailpoint from "../../sailpoint.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "sailpoint-list-certification-campaigns", name: "List Certification Campaigns", - description: "Retrieves multiple certification campaigns in IdentityNow. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Retrieves multiple certification campaigns in IdentityNow. [See the documentation](https://developer.sailpoint.com/docs/api/v2024/get-active-campaigns)", + version: "0.0.1", type: "action", props: { - sailpoint: { - type: "app", - app: "identitynow", - }, + sailpoint, filter: { propDefinition: [ "sailpoint", @@ -21,8 +17,20 @@ export default { }, }, async run({ $ }) { - const campaigns = await this.sailpoint.retrieveCertificationCampaigns(); - $.export("$summary", `Successfully retrieved ${campaigns.length} certification campaigns`); - return campaigns; + const response = this.sailpoint.paginate({ + fn: this.sailpoint.listCertificationCampaigns, + params: { + detail: "full", + }, + }); + + const responseArray = []; + + for await (const item of response) { + responseArray.push(item); + } + + $.export("$summary", `Successfully retrieved ${responseArray.length} certification campaigns`); + return responseArray; }, }; diff --git a/components/sailpoint/actions/submit-access-request/submit-access-request.mjs b/components/sailpoint/actions/submit-access-request/submit-access-request.mjs index 9689cf724fc36..72214e06bc75f 100644 --- a/components/sailpoint/actions/submit-access-request/submit-access-request.mjs +++ b/components/sailpoint/actions/submit-access-request/submit-access-request.mjs @@ -1,11 +1,11 @@ import identitynow from "../../identitynow.app.mjs"; -import { axios } from "@pipedream/platform"; +import { REQUEST_TYPE_OPTIONS } from "./common/constants.mjs"; export default { key: "identitynow-submit-access-request", name: "Submit Access Request", description: "Sends an access request to IdentityNow. [See the documentation](https://developer.sailpoint.com/docs/api/v2024/create-access-request)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { identitynow, @@ -16,21 +16,34 @@ export default { ], }, requestType: { - propDefinition: [ - identitynow, - "requestType", - ], - optional: true, + type: "string", + label: "Request Type", + description: "Type of access request.", + options: REQUEST_TYPE_OPTIONS, + default: REQUEST_TYPE_OPTIONS[0].value, }, requestedItems: { - propDefinition: [ - identitynow, - "requestedItems", - ], + type: "string[]", + label: "Requested Items", + description: "List of requested items as JSON strings. **Example: [{\"type\": \"ROLE\",\"id\": \"2c9180835d2e5168015d32f890ca1581\",\"comment\": \"Requesting access profile for John Doe\",\"clientMetadata\": {\"requestedAppId\":\"2c91808f7892918f0178b78da4a305a1\",\"requestedAppName\":\"test-app\"},\"removeDate\": \"2020-07-11T21:23:15.000Z\"}]**. [See the documentation](https://developer.sailpoint.com/docs/api/v2024/create-access-request) for forther information.", + }, + clientMetadata: { + type: "object", + label: "Client Metadata", + description: "Arbitrary key-value pairs. They will never be processed by the IdentityNow system but will be returned on associated APIs such as /account-activities. **Example: {\"requestedAppId\":\"2c91808f7892918f0178b78da4a305a1\",\"requestedAppName\":\"test-app\"}**.", + optional: true, }, }, async run({ $ }) { - const response = await this.identitynow.submitAccessRequest(); + const response = await this.identitynow.submitAccessRequest({ + $, + data: { + requestedFor: this.requestedFor, + requestType: this.requestType, + resquestItems: this.resquestItems, + clientMetadata: this.clientMetadata, + }, + }); $.export("$summary", "Access request submitted successfully."); return response; }, diff --git a/components/sailpoint/actions/upload-account-source-file/upload-account-source-file.mjs b/components/sailpoint/actions/upload-account-source-file/upload-account-source-file.mjs index 480c70cea87cf..7d6341dd8b07d 100644 --- a/components/sailpoint/actions/upload-account-source-file/upload-account-source-file.mjs +++ b/components/sailpoint/actions/upload-account-source-file/upload-account-source-file.mjs @@ -1,11 +1,13 @@ +import FormData from "form-data"; +import fs from "fs"; +import { checkTmp } from "../../common/utils.mjs"; import sailpoint from "../../sailpoint.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "sailpoint-upload-account-source-file", name: "Upload Account Source File", description: "Uploads a CSV-formatted account source file to IdentityNow. [See the documentation]()", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { sailpoint, @@ -15,16 +17,25 @@ export default { "sourceId", ], }, - csvAccountFile: { - propDefinition: [ - sailpoint, - "csvAccountFile", - ], + filePath: { + type: "string", + label: "File Path", + description: "The path to a file in the `/tmp` directory. [See the documentation on working with files](https://pipedream.com/docs/code/nodejs/working-with-files/#writing-a-file-to-tmp).", }, }, async run({ $ }) { - const response = await this.sailpoint.uploadCsvAccountFile(); - $.export("$summary", `Successfully uploaded CSV account file for source ${response.id} (${response.name})`); + const data = new FormData(); + + data.append("file", fs.createReadStream(checkTmp(this.filePath))); + + const response = await this.sailpoint.uploadSourceAccountFile({ + $, + sourceId: this.sourceId, + data, + headers: data.getHeaders(), + }); + + $.export("$summary", `Successfully uploaded file for source account: ${response.id} (${response.name})`); return response; }, }; diff --git a/components/sailpoint/app/sailpoint.app.ts b/components/sailpoint/app/sailpoint.app.ts deleted file mode 100644 index 30c1a55ca46d1..0000000000000 --- a/components/sailpoint/app/sailpoint.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "sailpoint", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); diff --git a/components/sailpoint/common/constants.mjs b/components/sailpoint/common/constants.mjs new file mode 100644 index 0000000000000..76f7ac549e55a --- /dev/null +++ b/components/sailpoint/common/constants.mjs @@ -0,0 +1,20 @@ +export const LIMIT = 100; + +export const REQUEST_TYPE_OPTIONS = [ + { + label: "Grant Access", + value: "GRANT_ACCESS", + }, + { + label: "Revoke Access", + value: "REVOKE_ACCESS", + }, +]; + +export const WEBHOOK_TYPE_OPTIONS = [ + "HTTP", + "EVENTBRIDGE", + "INLINE", + "SCRIPT", + "WORKFLOW", +]; diff --git a/components/sailpoint/common/utils.mjs b/components/sailpoint/common/utils.mjs new file mode 100644 index 0000000000000..a8b2f02966833 --- /dev/null +++ b/components/sailpoint/common/utils.mjs @@ -0,0 +1,8 @@ +export default { + checkTmp(filename) { + if (!filename.startsWith("/tmp")) { + return `/tmp/${filename}`; + } + return filename; + }, +}; diff --git a/components/sailpoint/package.json b/components/sailpoint/package.json index 25341643f4a5d..6c5e1453712be 100644 --- a/components/sailpoint/package.json +++ b/components/sailpoint/package.json @@ -1,16 +1,20 @@ { "name": "@pipedream/sailpoint", - "version": "0.0.3", + "version": "0.1.0", "description": "Pipedream SailPoint Components", - "main": "dist/app/sailpoint.app.mjs", + "main": "sailpoint.app.mjs", "keywords": [ "pipedream", "sailpoint" ], - "files": ["dist"], "homepage": "https://pipedream.com/apps/sailpoint", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "fs": "^0.0.1-security" } } + diff --git a/components/sailpoint/sailpoint.app.mjs b/components/sailpoint/sailpoint.app.mjs index 7bf1bc62c84a6..4e5f9c4cdbe1e 100644 --- a/components/sailpoint/sailpoint.app.mjs +++ b/components/sailpoint/sailpoint.app.mjs @@ -1,116 +1,145 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", app: "identitynow", propDefinitions: { - // Props for uploading CSV account file - sourceId: { - type: "string", - label: "Source ID", - description: "ID of the source to upload the account file.", - }, - csvAccountFile: { - type: "string", - label: "CSV Account File", - description: "CSV-formatted account file content.", - }, - // Props for retrieving certification campaigns - filter: { - type: "string", - label: "Filter", - description: "Optional filter to adjust campaign retrieval.", - optional: true, - }, - // Props for submitting access request requestedFor: { type: "string[]", label: "Requested For", description: "List of Identity IDs for whom the Access is requested.", + async options({ page }) { + const data = await this.listIdentityIds({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - requestType: { + sourceId: { type: "string", - label: "Request Type", - description: "Type of access request.", - options: [ - { - label: "Grant Access", - value: "GRANT_ACCESS", - }, - { - label: "Revoke Access", - value: "REVOKE_ACCESS", - }, - ], - optional: true, - }, - requestedItems: { - type: "string[]", - label: "Requested Items", - description: "List of requested items as JSON strings.", + label: "Source ID", + description: "ID of the source to upload the account file.", + async options({ page }) { + const data = await this.listSoruceIds({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, }, methods: { _baseUrl() { return "https://sailpoint.api.identitynow.com/v2024"; }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _headers(headers = {}) { + return { + ...headers, + Accept: "application/json", + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, headers, ...opts + }) { return axios($, { - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.oauth_access_token}`, - }, - ...otherOpts, + headers: this._headers(headers), + ...opts, }); }, - async uploadCsvAccountFile() { - const { - sourceId, csvAccountFile, - } = this; - const formData = new FormData(); - formData.append("file", csvAccountFile, "accounts.csv"); + listIdentityIds(opts = {}) { return this._makeRequest({ - method: "POST", - path: `/v3/sources/${sourceId}/schemas/accounts`, + path: "/identities", headers: { - ...formData.getHeaders(), - "X-SailPoint-Experimental": "true", + "X-SailPoint-Experimental": true, }, - data: formData, + ...opts, }); }, - async retrieveCertificationCampaigns() { - const { filter } = this; - const params = {}; - if (filter) { - params.filter = filter; - } + listSoruceIds(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/campaigns", - params, + path: "/sources", + ...opts, }); }, - async submitAccessRequest() { - const { - requestedFor, requestType, requestedItems, - } = this; - const data = { - requestedFor, - requestType: requestType || "GRANT_ACCESS", - requestedItems: requestedItems.map((item) => JSON.parse(item)), - }; + submitAccessRequest(opts = {}) { return this._makeRequest({ method: "POST", path: "/access-requests", - data, + ...opts, }); }, + uploadSourceAccountFile({ + sourceId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sources/${sourceId}/schemas/accounts`, + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/trigger-subscriptions", + headers: { + "X-SailPoint-Experimental": true, + }, + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "POST", + path: `/trigger-subscriptions/${webhookId}`, + headers: { + "X-SailPoint-Experimental": true, + }, + }); + }, + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = ++page; + const data = await fn({ + params, + ...opts, + }); + for (const d of data) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = data.length; + + } while (hasMore); }, - version: `0.0.${Date.now()}`, }; diff --git a/components/sailpoint/sources/common/base.mjs b/components/sailpoint/sources/common/base.mjs new file mode 100644 index 0000000000000..c098d0f3a1762 --- /dev/null +++ b/components/sailpoint/sources/common/base.mjs @@ -0,0 +1,74 @@ +import sailpoint from "../../sailpoint.app.mjs"; + +export default { + props: { + sailpoint, + http: "$.interface.http", + db: "$.service.db", + name: { + type: "string", + label: "Name", + description: "Subscription name.", + }, + description: { + type: "string", + label: "Description", + description: "Subscription description.", + optional: true, + }, + responseDeadline: { + type: "string", + label: "Response Deadline", + description: "Deadline for completing REQUEST_RESPONSE trigger invocation, represented in ISO-8601 duration format.", + default: "PT1H", + optional: true, + }, + filter: { + type: "string", + label: "Filter", + description: "JSONPath filter to conditionally invoke trigger when expression evaluates to true. **Example: $[?($.identityId == \"201327fda1c44704ac01181e963d463c\")]**. [See the documentation](https://developer.sailpoint.com/docs/extensibility/event-triggers/filtering-events/) for further information.", + optional: true, + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + }, + hooks: { + async activate() { + const response = await this.sailpoint.createWebhook({ + data: { + name: this.name, + description: this.description, + triggerId: this.getTriggerId(), + type: "HTTP", + responseDeadline: this.responseDeadline, + httpConfig: { + url: this.http.endpoint, + httpDispatchMode: "ASYNC", + httpAuthenticationType: "NO_AUTH", + }, + enabled: true, + filter: this.filter, + }, + }); + this._setHookId(response.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.sailpoint.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + const ts = Date.parse(body.completed || new Date()); + this.$emit(body, { + id: `${body.source.id}-${ts}`, + summary: this.getSummary(body), + ts: ts, + }); + }, +}; diff --git a/components/sailpoint/sources/identity-attribute-change-instant/identity-attribute-change-instant.mjs b/components/sailpoint/sources/identity-attribute-change-instant/identity-attribute-change-instant.mjs index d9a1f9551f849..18254c93e45c0 100644 --- a/components/sailpoint/sources/identity-attribute-change-instant/identity-attribute-change-instant.mjs +++ b/components/sailpoint/sources/identity-attribute-change-instant/identity-attribute-change-instant.mjs @@ -1,98 +1,22 @@ -import sailpoint from "../../sailpoint.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "sailpoint-identity-attribute-change-instant", - name: "Identity Attribute Change Instant", - description: "Emit new event when any attributes of an identity change. [See the documentation]()", - version: "0.0.{{ts}}", + name: "New Identity Attribute Change (Instant)", + description: "Emit new event when any attributes of an identity change.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - sailpoint: { - type: "app", - app: "sailpoint", - }, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - }, methods: { - async createWebhook() { - const callbackUrl = this.http.endpoint; - const response = await this.sailpoint._makeRequest({ - method: "POST", - path: "/webhooks", - data: { - triggerId: "idn:identity-attributes-changed", - callbackUrl: callbackUrl, - }, - }); - return response.id; + ...common.methods, + getTriggerId() { + return "idn:identity-attributes-changed"; }, - async deleteWebhook(webhookId) { - await this.sailpoint._makeRequest({ - method: "DELETE", - path: `/webhooks/${webhookId}`, - }); + getSummary(event) { + return `Attributes changed for identity ${event.source.id}`; }, - async listIdentityAttributeChanges() { - const response = await this.sailpoint._makeRequest({ - method: "GET", - path: "/identity-attributes-changed", - params: { - limit: 50, - sort: "-timestamp", - }, - }); - return response.events || []; - }, - }, - hooks: { - async deploy() { - const events = await this.listIdentityAttributeChanges(); - for (const event of events.reverse()) { - this.$emit(event.attributes, { - id: event.id || event.timestamp.toString(), - summary: `Attributes changed for identity ${event.identityId}`, - ts: new Date(event.timestamp).getTime(), - }); - } - }, - async activate() { - const webhookId = await this.createWebhook(); - await this.db.set("webhookId", webhookId); - }, - async deactivate() { - const webhookId = await this.db.get("webhookId"); - if (webhookId) { - await this.deleteWebhook(webhookId); - await this.db.set("webhookId", null); - } - }, - }, - async run(event) { - const signature = event.headers["X-Signature"] || event.headers["x-signature"]; - const secretKey = this.sailpoint.$auth.secret; - const computedSignature = crypto - .createHmac("sha256", secretKey) - .update(event.body) - .digest("base64"); - if (computedSignature !== signature) { - await this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - const data = JSON.parse(event.body); - this.$emit(data.changedAttributes, { - id: data.id || data.timestamp.toString(), - summary: `Attributes changed for identity ${data.identityId}`, - ts: Date.parse(data.timestamp) || Date.now(), - }); }, + sampleEmit, }; diff --git a/components/sailpoint/sources/identity-attribute-change-instant/test-event.mjs b/components/sailpoint/sources/identity-attribute-change-instant/test-event.mjs new file mode 100644 index 0000000000000..40e4bd4cabc2d --- /dev/null +++ b/components/sailpoint/sources/identity-attribute-change-instant/test-event.mjs @@ -0,0 +1,19 @@ +export default { + "source": { + "type": "IDENTITY", + "id": "2c91808568c529c60168cca6f90c1313", + "name": "William Wilson" + }, + "status": "Success", + "started": "2020-06-29T22:01:50.474Z", + "completed": "2020-06-29T22:02:04.090Z", + "errors": ["Accounts unable to be aggregated."], + "warnings": ["Account Skipped"], + "stats": { + "scanned": 200, + "unchanged": 190, + "changed": 6, + "added": 4, + "removed": 3 + } +} \ No newline at end of file diff --git a/components/sailpoint/sources/new-identity-instant/new-identity-instant.mjs b/components/sailpoint/sources/new-identity-instant/new-identity-instant.mjs index eda6563c8dc8e..c5f9ecee92853 100644 --- a/components/sailpoint/sources/new-identity-instant/new-identity-instant.mjs +++ b/components/sailpoint/sources/new-identity-instant/new-identity-instant.mjs @@ -1,93 +1,22 @@ -import sailpoint from "../../sailpoint.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "sailpoint-new-identity-instant", - name: "New Identity Created", - description: "Emit new event when a new identity is created in IdentityNow. [See the documentation](${{docsLink}})", - version: "0.0.{{ts}}", + name: "New Identity Created (Instant)", + description: "Emit new event when a new identity is created in IdentityNow.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - sailpoint, - http: { - type: "$.interface.http", - customResponse: false, + methods: { + ...common.methods, + getTriggerId() { + return "idn:identity-created"; }, - db: "$.service.db", - }, - hooks: { - async deploy() { - const params = { - limit: 50, - sort: "createdAt desc", - }; - const identities = await this.sailpoint._makeRequest({ - method: "GET", - path: "/identities", - params, - }); - - for (const identity of identities) { - this.$emit(identity, { - id: identity.identityId || identity.id, - summary: `New identity created: ${identity.name}`, - ts: Date.parse(identity.createdAt) || Date.now(), - }); - } + getSummary(event) { + return `New identity created: ${event.identity.name}`; }, - async activate() { - const webhookUrl = this.http.endpoint; - - const body = { - name: "New Identity Created Subscription", - description: "Subscription for new identity creation events", - triggerId: "idn:identity-created", - type: "HTTP", - responseDeadline: "PT1H", - httpConfig: { - url: webhookUrl, - httpDispatchMode: "SYNC", - httpAuthenticationType: "NO_AUTH", - }, - enabled: true, - }; - - const headers = { - "X-SailPoint-Experimental": "true", - }; - - const response = await this.sailpoint._makeRequest({ - method: "POST", - path: "/trigger-subscriptions", - headers, - data: body, - }); - - const subscriptionId = response.id; - this.db.set("subscriptionId", subscriptionId); - }, - async deactivate() { - const subscriptionId = await this.db.get("subscriptionId"); - if (subscriptionId) { - const headers = { - "X-SailPoint-Experimental": "true", - }; - await this.sailpoint._makeRequest({ - method: "DELETE", - path: `/trigger-subscriptions/${subscriptionId}`, - headers, - }); - await this.db.delete("subscriptionId"); - } - }, - }, - async run(event) { - const identity = event; - this.$emit(identity, { - id: identity.identityId || identity.id, - summary: `New identity created: ${identity.name}`, - ts: Date.parse(identity.createdAt) || Date.now(), - }); }, + sampleEmit, }; diff --git a/components/sailpoint/sources/new-identity-instant/test-event.mjs b/components/sailpoint/sources/new-identity-instant/test-event.mjs new file mode 100644 index 0000000000000..696612b1eff05 --- /dev/null +++ b/components/sailpoint/sources/new-identity-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "identity": { + "type": "IDENTITY", + "id": "2c91808568c529c60168cca6f90c1313", + "name": "William Wilson" + }, + "attributes": { + "firstname": "John" + } +} \ No newline at end of file