From 6488ad4e7672a4d3af1f2426c1a1fb27c9e11093 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 13 Feb 2025 13:58:30 -0300 Subject: [PATCH 1/6] onelogin init --- .../actions/create-user/create-user.mjs | 232 +++++++++++ .../revoke-user-sessions.mjs | 23 ++ .../actions/update-user/update-user.mjs | 224 +++++++++++ components/onelogin/onelogin.app.mjs | 368 +++++++++++++++++- components/onelogin/package.json | 2 +- .../new-directory-sync-event.mjs | 104 +++++ .../new-login-attempt/new-login-attempt.mjs | 124 ++++++ .../onelogin/sources/new-user/new-user.mjs | 102 +++++ 8 files changed, 1176 insertions(+), 3 deletions(-) create mode 100644 components/onelogin/actions/create-user/create-user.mjs create mode 100644 components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs create mode 100644 components/onelogin/actions/update-user/update-user.mjs create mode 100644 components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs create mode 100644 components/onelogin/sources/new-login-attempt/new-login-attempt.mjs create mode 100644 components/onelogin/sources/new-user/new-user.mjs diff --git a/components/onelogin/actions/create-user/create-user.mjs b/components/onelogin/actions/create-user/create-user.mjs new file mode 100644 index 0000000000000..7f3a52a1d4a8e --- /dev/null +++ b/components/onelogin/actions/create-user/create-user.mjs @@ -0,0 +1,232 @@ +import onelogin from "../../onelogin.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "onelogin-create-user", + name: "Create User", + description: "Creates a new user in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/1/users/create-user)", + version: "0.0.{{ts}}", + type: "action", + props: { + onelogin, + firstname: { + propDefinition: [ + onelogin, + "firstname", + ], + }, + lastname: { + propDefinition: [ + onelogin, + "lastname", + ], + }, + email: { + propDefinition: [ + onelogin, + "email", + ], + optional: true, + }, + username: { + propDefinition: [ + onelogin, + "username", + ], + optional: true, + }, + company: { + propDefinition: [ + onelogin, + "company", + ], + optional: true, + }, + department: { + propDefinition: [ + onelogin, + "department", + ], + optional: true, + }, + directoryId: { + propDefinition: [ + onelogin, + "directoryId", + ], + optional: true, + }, + distinguishedName: { + propDefinition: [ + onelogin, + "distinguishedName", + ], + optional: true, + }, + externalId: { + propDefinition: [ + onelogin, + "externalId", + ], + optional: true, + }, + groupId: { + propDefinition: [ + onelogin, + "groupId", + ], + optional: true, + }, + invalidLoginAttempts: { + propDefinition: [ + onelogin, + "invalidLoginAttempts", + ], + optional: true, + }, + localeCode: { + propDefinition: [ + onelogin, + "localeCode", + ], + optional: true, + }, + memberOf: { + propDefinition: [ + onelogin, + "memberOf", + ], + optional: true, + }, + openidName: { + propDefinition: [ + onelogin, + "openidName", + ], + optional: true, + }, + phone: { + propDefinition: [ + onelogin, + "phone", + ], + optional: true, + }, + samaccountname: { + propDefinition: [ + onelogin, + "samaccountname", + ], + optional: true, + }, + title: { + propDefinition: [ + onelogin, + "title", + ], + optional: true, + }, + customAttributes: { + propDefinition: [ + onelogin, + "customAttributes", + ], + optional: true, + }, + }, + async run({ $ }) { + if (!this.email && !this.username) { + throw new Error("Either email or username must be provided."); + } + + const userData = { + firstname: this.firstname, + lastname: this.lastname, + ...(this.email + ? { + email: this.email, + } + : {}), + ...(this.username + ? { + username: this.username, + } + : {}), + ...(this.company != null + ? { + company: this.company, + } + : {}), + ...(this.department != null + ? { + department: this.department, + } + : {}), + ...(this.directoryId != null + ? { + directory_id: this.directoryId, + } + : {}), + ...(this.distinguishedName != null + ? { + distinguished_name: this.distinguishedName, + } + : {}), + ...(this.externalId != null + ? { + external_id: this.externalId, + } + : {}), + ...(this.groupId != null + ? { + group_id: parseInt(this.groupId, 10), + } + : {}), + ...(this.invalidLoginAttempts != null + ? { + invalid_login_attempts: this.invalidLoginAttempts, + } + : {}), + ...(this.localeCode != null + ? { + locale_code: this.localeCode, + } + : {}), + ...(this.memberOf != null + ? { + member_of: this.memberOf, + } + : {}), + ...(this.openidName != null + ? { + openid_name: this.openidName, + } + : {}), + ...(this.phone != null + ? { + phone: this.phone, + } + : {}), + ...(this.samaccountname != null + ? { + samaccountname: this.samaccountname, + } + : {}), + ...(this.title != null + ? { + title: this.title, + } + : {}), + ...(this.customAttributes != null + ? { + custom_attributes: this.customAttributes, + } + : {}), + }; + + const response = await this.onelogin.createUser(userData); + + $.export("$summary", `Created user ${response.username} with ID ${response.id}`); + return response; + }, +}; diff --git a/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs b/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs new file mode 100644 index 0000000000000..0f71ad5fc9272 --- /dev/null +++ b/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs @@ -0,0 +1,23 @@ +import onelogin from "../../onelogin.app.mjs"; + +export default { + key: "onelogin-revoke-user-sessions", + name: "Revoke User Sessions", + description: "Revokes all active sessions for a specified user in OneLogin. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + onelogin, + revokeUserId: { + propDefinition: [ + onelogin, + "revokeUserId", + ], + }, + }, + async run({ $ }) { + const response = await this.onelogin.revokeUserSessions(this.revokeUserId); + $.export("$summary", `Successfully revoked sessions for user ID ${this.revokeUserId}`); + return response; + }, +}; diff --git a/components/onelogin/actions/update-user/update-user.mjs b/components/onelogin/actions/update-user/update-user.mjs new file mode 100644 index 0000000000000..bd86bf0a781b1 --- /dev/null +++ b/components/onelogin/actions/update-user/update-user.mjs @@ -0,0 +1,224 @@ +import onelogin from "../../onelogin.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "onelogin-update-user", + name: "Update User", + description: "Updates an existing user's details in OneLogin. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + onelogin, + updateUserId: { + propDefinition: [ + onelogin, + "updateUserId", + ], + }, + firstname: { + propDefinition: [ + onelogin, + "firstname", + ], + optional: true, + }, + lastname: { + propDefinition: [ + onelogin, + "lastname", + ], + optional: true, + }, + email: { + propDefinition: [ + onelogin, + "email", + ], + optional: true, + }, + username: { + propDefinition: [ + onelogin, + "username", + ], + optional: true, + }, + company: { + propDefinition: [ + onelogin, + "company", + ], + optional: true, + }, + department: { + propDefinition: [ + onelogin, + "department", + ], + optional: true, + }, + directoryId: { + propDefinition: [ + onelogin, + "directoryId", + ], + optional: true, + }, + distinguishedName: { + propDefinition: [ + onelogin, + "distinguishedName", + ], + optional: true, + }, + externalId: { + propDefinition: [ + onelogin, + "externalId", + ], + optional: true, + }, + groupId: { + propDefinition: [ + onelogin, + "groupId", + ], + optional: true, + }, + invalidLoginAttempts: { + propDefinition: [ + onelogin, + "invalidLoginAttempts", + ], + optional: true, + }, + localeCode: { + propDefinition: [ + onelogin, + "localeCode", + ], + optional: true, + }, + memberOf: { + propDefinition: [ + onelogin, + "memberOf", + ], + optional: true, + }, + openidName: { + propDefinition: [ + onelogin, + "openidName", + ], + optional: true, + }, + phone: { + propDefinition: [ + onelogin, + "phone", + ], + optional: true, + }, + samaccountname: { + propDefinition: [ + onelogin, + "samaccountname", + ], + optional: true, + }, + title: { + propDefinition: [ + onelogin, + "title", + ], + optional: true, + }, + customAttributes: { + propDefinition: [ + onelogin, + "customAttributes", + ], + optional: true, + }, + state: { + propDefinition: [ + onelogin, + "state", + ], + optional: true, + }, + status: { + propDefinition: [ + onelogin, + "status", + ], + optional: true, + }, + userprincipalname: { + propDefinition: [ + onelogin, + "userprincipalname", + ], + optional: true, + }, + managerAdId: { + propDefinition: [ + onelogin, + "manager_ad_id", + ], + optional: true, + }, + managerUserId: { + propDefinition: [ + onelogin, + "manager_user_id", + ], + optional: true, + }, + roleId: { + propDefinition: [ + onelogin, + "role_id", + ], + optional: true, + }, + }, + async run({ $ }) { + const userId = this.updateUserId; + const data = {}; + + if (this.firstname) data.firstname = this.firstname; + if (this.lastname) data.lastname = this.lastname; + if (this.email) data.email = this.email; + if (this.username) data.username = this.username; + if (this.company) data.company = this.company; + if (this.department) data.department = this.department; + if (this.directoryId !== undefined) data.directory_id = this.directoryId; + if (this.distinguishedName) data.distinguished_name = this.distinguishedName; + if (this.externalId) data.external_id = this.externalId; + if (this.groupId) data.group_id = parseInt(this.groupId, 10); + if (this.invalidLoginAttempts !== undefined) data.invalid_login_attempts = this.invalidLoginAttempts; + if (this.localeCode) data.locale_code = this.localeCode; + if (this.memberOf) data.member_of = this.memberOf; + if (this.openidName) data.openid_name = this.openidName; + if (this.phone) data.phone = this.phone; + if (this.samaccountname) data.samaccountname = this.samaccountname; + if (this.title) data.title = this.title; + if (this.customAttributes) data.custom_attributes = this.customAttributes; + if (this.state !== undefined) data.state = this.state; + if (this.status !== undefined) data.status = this.status; + if (this.userprincipalname) data.userprincipalname = this.userprincipalname; + if (this.managerAdId) data.manager_ad_id = this.managerAdId; + if (this.managerUserId !== undefined) data.manager_user_id = this.managerUserId; + if (this.roleId) { + data.role_id = Array.isArray(this.roleId) + ? this.roleId.map((id) => parseInt(id, 10)) + : parseInt(this.roleId, 10); + } + + const response = await this.onelogin.updateUser(userId, data); + $.export("$summary", `Successfully updated user with ID ${userId}`); + return response; + }, +}; diff --git a/components/onelogin/onelogin.app.mjs b/components/onelogin/onelogin.app.mjs index bc625b9d09792..c88ee2c3f3732 100644 --- a/components/onelogin/onelogin.app.mjs +++ b/components/onelogin/onelogin.app.mjs @@ -1,11 +1,375 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "onelogin", - propDefinitions: {}, + propDefinitions: { + // Required props for creating a new user + firstname: { + type: "string", + label: "First Name", + description: "User's first name.", + }, + lastname: { + type: "string", + label: "Last Name", + description: "User's last name.", + }, + email: { + type: "string", + label: "Email", + description: "User's email address.", + optional: true, + }, + username: { + type: "string", + label: "Username", + description: "User's username.", + optional: true, + }, + // Optional props for creating/updating a user + company: { + type: "string", + label: "Company", + description: "Company the user is associated with.", + optional: true, + }, + department: { + type: "string", + label: "Department", + description: "Department the user works in.", + optional: true, + }, + directoryId: { + type: "integer", + label: "Directory ID", + description: "ID of the directory (e.g., Active Directory, LDAP) from which the user was created.", + optional: true, + }, + distinguishedName: { + type: "string", + label: "Distinguished Name", + description: "Synchronized from Active Directory.", + optional: true, + }, + externalId: { + type: "string", + label: "External ID", + description: "External ID that can be used to uniquely identify the user in another system.", + optional: true, + }, + groupId: { + type: "string", + label: "Group ID", + description: "Group to which the user belongs.", + async options() { + const groups = await this.listGroups(); + return groups.map((group) => ({ + label: group.name, + value: group.id.toString(), + })); + }, + optional: true, + }, + invalidLoginAttempts: { + type: "integer", + label: "Invalid Login Attempts", + description: "Number of sequential invalid login attempts the user has made.", + optional: true, + }, + localeCode: { + type: "string", + label: "Locale Code", + description: "Locale code representing a geographical, political, or cultural region.", + optional: true, + }, + memberOf: { + type: "string", + label: "Member Of", + description: "Groups the user is a member of.", + optional: true, + }, + openidName: { + type: "string", + label: "OpenID Name", + description: "OpenID URL that can be configured in other applications that accept OpenID for sign-in.", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "User's phone number.", + optional: true, + }, + samaccountname: { + type: "string", + label: "SAMAccountName", + description: "Synchronized from Active Directory.", + optional: true, + }, + title: { + type: "string", + label: "Title", + description: "User's title.", + optional: true, + }, + customAttributes: { + type: "object", + label: "Custom Attributes", + description: "Custom attributes for the user.", + optional: true, + }, + // Props for updating an existing user + updateUserId: { + type: "string", + label: "User ID", + description: "Unique ID of the user to update.", + async options() { + const users = await this.listUsers(); + return users.map((user) => ({ + label: `${user.firstname} ${user.lastname} (${user.email})`, + value: user.id.toString(), + })); + }, + }, + // Props for revoking user sessions + revokeUserId: { + type: "string", + label: "User ID", + description: "Unique ID of the user to revoke sessions for.", + async options() { + const users = await this.listUsers(); + return users.map((user) => ({ + label: `${user.firstname} ${user.lastname} (${user.email})`, + value: user.id.toString(), + })); + }, + }, + // Props for User Created Event Trigger filters + filterUserRole: { + type: "string", + label: "User Role", + description: "Filter by user role.", + async options() { + const roles = await this.listRoles(); + return roles.map((role) => ({ + label: role.name, + value: role.id.toString(), + })); + }, + optional: true, + }, + filterGroup: { + type: "string", + label: "Group", + description: "Filter by user group.", + async options() { + const groups = await this.listGroups(); + return groups.map((group) => ({ + label: group.name, + value: group.id.toString(), + })); + }, + optional: true, + }, + // Props for Login Attempt Event Trigger filters + filterLoginSuccess: { + type: "boolean", + label: "Successful Attempts", + description: "Filter to only include successful login attempts.", + optional: true, + }, + filterLoginFailure: { + type: "boolean", + label: "Failed Attempts", + description: "Filter to only include failed login attempts.", + optional: true, + }, + // Props for Directory Sync Event Trigger filters + directoryName: { + type: "string", + label: "Directory Name", + description: "Filter by specific directory name.", + async options() { + const directories = await this.listDirectories(); + return directories.map((dir) => ({ + label: dir.name, + value: dir.id.toString(), + })); + }, + optional: true, + }, + syncStatus: { + type: "string", + label: "Sync Status", + description: "Filter by sync status.", + options: [ + { + label: "Success", + value: "success", + }, + { + label: "Failure", + value: "failure", + }, + { + label: "In Progress", + value: "in_progress", + }, + ], + optional: true, + }, + }, methods: { - // this.$auth contains connected account data + // Existing method authKeys() { console.log(Object.keys(this.$auth)); }, + // Base URL + _baseUrl() { + return "https://api.onelogin.com/api/1"; + }, + // Make Request + async _makeRequest(opts = {}) { + const { + $ = this, + method = "GET", + path = "/", + data, + params, + headers = {}, + ...otherOpts + } = opts; + return axios($, { + method, + url: this._baseUrl() + path, + data: data, + params: params, + headers: { + ...headers, + "Authorization": `bearer:${this.$auth.access_token}`, + "Content-Type": "application/json", + }, + ...otherOpts, + }); + }, + // Pagination Logic + async paginate(fn, ...args) { + const results = []; + let page = 1; + let hasNext = true; + while (hasNext) { + const response = await fn({ + page, + ...args, + }); + if (Array.isArray(response)) { + results.push(...response); + hasNext = false; + } else { + results.push(...response); + hasNext = false; + } + } + return results; + }, + // List Groups + async listGroups(opts = {}) { + return this.paginate(async ({ page }) => { + const response = await this._makeRequest({ + path: "/groups", + params: { + limit: 100, + page, + ...opts.params, + }, + }); + return response; + }, opts); + }, + // List Roles + async listRoles(opts = {}) { + return this.paginate(async ({ page }) => { + const response = await this._makeRequest({ + path: "/roles", + params: { + limit: 100, + page, + ...opts.params, + }, + }); + return response; + }, opts); + }, + // List Users + async listUsers(opts = {}) { + return this.paginate(async ({ page }) => { + const response = await this._makeRequest({ + path: "/users", + params: { + limit: 100, + page, + ...opts.params, + }, + }); + return response; + }, opts); + }, + // List Directories + async listDirectories(opts = {}) { + return this.paginate(async ({ page }) => { + const response = await this._makeRequest({ + path: "/directories", + params: { + limit: 100, + page, + ...opts.params, + }, + }); + return response; + }, opts); + }, + // Create User + async createUser(data, opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/users", + data, + ...opts, + }); + }, + // Update User + async updateUser(userId, data, opts = {}) { + return this._makeRequest({ + method: "PUT", + path: `/users/${userId}`, + data, + ...opts, + }); + }, + // Revoke User Sessions + async revokeUserSessions(userId, opts = {}) { + return this._makeRequest({ + method: "POST", + path: `/users/${userId}/logout`, + ...opts, + }); + }, + // Emit User Created Event + async emitUserCreatedEvent(filters) { + // Implementation to emit event based on filters + // This would typically involve setting up a webhook or listening to API events + }, + // Emit Login Attempt Event + async emitLoginAttemptEvent(filters) { + // Implementation to emit event based on filters + // This would typically involve setting up a webhook or listening to API events + }, + // Emit Directory Sync Event + async emitDirectorySyncEvent(filters) { + // Implementation to emit event based on filters + // This would typically involve setting up a webhook or listening to API events + }, }, + version: "0.0.ts", }; diff --git a/components/onelogin/package.json b/components/onelogin/package.json index 687cdf4ac603b..0945197d47fa2 100644 --- a/components/onelogin/package.json +++ b/components/onelogin/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs b/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs new file mode 100644 index 0000000000000..762de5d847b10 --- /dev/null +++ b/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs @@ -0,0 +1,104 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import onelogin from "../../onelogin.app.mjs"; + +export default { + key: "onelogin-new-directory-sync-event", + name: "New Directory Sync Event", + description: "Emit new event when a directory sync event is triggered in OneLogin. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + onelogin, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + directoryName: { + propDefinition: [ + onelogin, + "directoryName", + ], + optional: true, + }, + syncStatus: { + propDefinition: [ + onelogin, + "syncStatus", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const events = await this.runFetchEvents(); + const recentEvents = events.slice(-50).reverse(); + for (const event of recentEvents) { + this.$emit( + event, + { + id: event.id.toString(), + summary: `Directory Sync Event #${event.id}`, + ts: Date.parse(event.created_at), + }, + ); + } + const lastEvent = recentEvents[recentEvents.length - 1]; + if (lastEvent) { + await this.db.set("last_cursor", lastEvent.id); + } + }, + async activate() { + // No webhook setup required for polling source + }, + async deactivate() { + // No webhook teardown required for polling source + }, + }, + methods: { + async runFetchEvents() { + const lastCursor = await this.db.get("last_cursor"); + const params = { + event_type_id: 13, + limit: 50, + sort: "-id", + }; + if (lastCursor) { + params.after_cursor = lastCursor; + } + if (this.directoryName) { + params.directory_id = this.directoryName; + } + if (this.syncStatus) { + params.resolution = this.syncStatus; + } + const response = await this.onelogin._makeRequest({ + path: "/events", + params, + }); + return response; + }, + }, + async run() { + const events = await this.runFetchEvents(); + for (const event of events) { + this.$emit( + event, + { + id: event.id.toString(), + summary: `Directory Sync Event #${event.id}`, + ts: Date.parse(event.created_at), + }, + ); + } + if (events.length > 0) { + const lastEvent = events[events.length - 1]; + await this.db.set("last_cursor", lastEvent.id); + } + }, +}; diff --git a/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs b/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs new file mode 100644 index 0000000000000..f6e5f887e46e0 --- /dev/null +++ b/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs @@ -0,0 +1,124 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import onelogin from "../../onelogin.app.mjs"; + +export default { + key: "onelogin-new-login-attempt", + name: "New Login Attempt", + description: "Emit new event when a login attempt occurs in OneLogin. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + onelogin: { + type: "app", + app: "onelogin", + }, + db: { + type: "$.service.db", + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + filterLoginSuccess: { + propDefinition: [ + "onelogin", + "filterLoginSuccess", + ], + }, + filterLoginFailure: { + propDefinition: [ + "onelogin", + "filterLoginFailure", + ], + }, + }, + hooks: { + async deploy() { + await this.fetchAndEmitEvents(true); + }, + async activate() { + // No webhook setup required for polling source + }, + async deactivate() { + // No webhook cleanup required for polling source + }, + }, + methods: { + async fetchAndEmitEvents(isDeploy = false) { + const lastCursor = await this.db.get("after_cursor"); + const params = { + limit: 50, + sort: "-created_at", + }; + + if (lastCursor) { + params.after_cursor = lastCursor; + } + + // Define event_type_ids that correspond to login attempts + // These IDs should be replaced with actual ones from OneLogin + const loginAttemptEventTypeIds = [ + 5, + 13, + ]; + params.event_type_id = loginAttemptEventTypeIds.join(","); + + const response = await this.onelogin._makeRequest({ + path: "/events", + params, + }); + + const events = response; + + // Emit events from oldest to newest + for (const event of events.reverse()) { + if (this._isLoginAttempt(event) && this._matchesFilters(event)) { + const eventId = event.id + ? event.id.toString() + : undefined; + const eventTimestamp = event.created_at + ? Date.parse(event.created_at) + : Date.now(); + const emitMeta = { + id: eventId || undefined, + summary: `Login attempt by ${event.user_name}`, + ts: eventTimestamp, + }; + this.$emit(event, emitMeta); + } + } + + // Update the cursor + if (events.length > 0 && response.pagination && response.pagination.after_cursor) { + await this.db.set("after_cursor", response.pagination.after_cursor); + } + }, + _isLoginAttempt(event) { + return [ + 5, + 13, + ].includes(event.event_type_id); + }, + _matchesFilters(event) { + let isSuccess = !event.error_description; + if (this.filterLoginSuccess && isSuccess) { + return true; + } + if (this.filterLoginFailure && !isSuccess) { + return true; + } + if (!this.filterLoginSuccess && !this.filterLoginFailure) { + return true; + } + return false; + }, + }, + async run() { + await this.fetchAndEmitEvents(); + }, +}; diff --git a/components/onelogin/sources/new-user/new-user.mjs b/components/onelogin/sources/new-user/new-user.mjs new file mode 100644 index 0000000000000..6b6a60967cdda --- /dev/null +++ b/components/onelogin/sources/new-user/new-user.mjs @@ -0,0 +1,102 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import onelogin from "../../onelogin.app.mjs"; + +export default { + key: "onelogin-new-user", + name: "New User Created", + description: "Emit a new event when a user is created in OneLogin. [See the documentation]().", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + onelogin, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + filterUserRole: { + propDefinition: [ + onelogin, + "filterUserRole", + ], + optional: true, + }, + filterGroup: { + propDefinition: [ + onelogin, + "filterGroup", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const lastRun = this.db.get("lastRun"); + const now = new Date().toISOString(); + const since = lastRun || new Date(Date.now() - this.timer.intervalSeconds * 1000).toISOString(); + const params = { + event_type_id: "13", + since, + limit: 50, + sort: "-created_at", + }; + const events = await this.onelogin._makeRequest({ + path: "/events", + params, + }); + for (const event of events) { + if (this.filterUserRole && event.role_id !== parseInt(this.filterUserRole, 10)) { + continue; + } + if (this.filterGroup && event.group_id !== parseInt(this.filterGroup, 10)) { + continue; + } + this.$emit(event, { + id: event.id.toString(), + summary: `New user created: ${event.user_name}`, + ts: Date.parse(event.created_at) || Date.now(), + }); + } + this.db.set("lastRun", now); + }, + async activate() { + // No webhook setup required for polling source + }, + async deactivate() { + // No webhook cleanup required for polling source + }, + }, + async run() { + const lastRun = this.db.get("lastRun"); + const now = new Date().toISOString(); + const params = { + event_type_id: "13", + since: lastRun || new Date(Date.now() - this.timer.intervalSeconds * 1000).toISOString(), + limit: 100, + sort: "-created_at", + }; + const events = await this.onelogin._makeRequest({ + path: "/events", + params, + }); + for (const event of events) { + if (this.filterUserRole && event.role_id !== parseInt(this.filterUserRole, 10)) { + continue; + } + if (this.filterGroup && event.group_id !== parseInt(this.filterGroup, 10)) { + continue; + } + this.$emit(event, { + id: event.id.toString(), + summary: `New user created: ${event.user_name}`, + ts: Date.parse(event.created_at) || Date.now(), + }); + } + this.db.set("lastRun", now); + }, +}; From 077473881d556d7f41af1f3df8525834eaedbe02 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 14 Feb 2025 11:42:48 -0300 Subject: [PATCH 2/6] some adjusts --- .../actions/create-user/create-user.mjs | 114 ++++------------ components/onelogin/common/utils.mjs | 24 ++++ components/onelogin/onelogin.app.mjs | 124 ++++++------------ 3 files changed, 87 insertions(+), 175 deletions(-) create mode 100644 components/onelogin/common/utils.mjs diff --git a/components/onelogin/actions/create-user/create-user.mjs b/components/onelogin/actions/create-user/create-user.mjs index 7f3a52a1d4a8e..5847e19354679 100644 --- a/components/onelogin/actions/create-user/create-user.mjs +++ b/components/onelogin/actions/create-user/create-user.mjs @@ -1,5 +1,6 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { parseObject } from "../../common/utils.mjs"; import onelogin from "../../onelogin.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "onelogin-create-user", @@ -136,95 +137,32 @@ export default { }, async run({ $ }) { if (!this.email && !this.username) { - throw new Error("Either email or username must be provided."); + throw new ConfigurationError("Either email or username must be provided."); } - const userData = { - firstname: this.firstname, - lastname: this.lastname, - ...(this.email - ? { - email: this.email, - } - : {}), - ...(this.username - ? { - username: this.username, - } - : {}), - ...(this.company != null - ? { - company: this.company, - } - : {}), - ...(this.department != null - ? { - department: this.department, - } - : {}), - ...(this.directoryId != null - ? { - directory_id: this.directoryId, - } - : {}), - ...(this.distinguishedName != null - ? { - distinguished_name: this.distinguishedName, - } - : {}), - ...(this.externalId != null - ? { - external_id: this.externalId, - } - : {}), - ...(this.groupId != null - ? { - group_id: parseInt(this.groupId, 10), - } - : {}), - ...(this.invalidLoginAttempts != null - ? { - invalid_login_attempts: this.invalidLoginAttempts, - } - : {}), - ...(this.localeCode != null - ? { - locale_code: this.localeCode, - } - : {}), - ...(this.memberOf != null - ? { - member_of: this.memberOf, - } - : {}), - ...(this.openidName != null - ? { - openid_name: this.openidName, - } - : {}), - ...(this.phone != null - ? { - phone: this.phone, - } - : {}), - ...(this.samaccountname != null - ? { - samaccountname: this.samaccountname, - } - : {}), - ...(this.title != null - ? { - title: this.title, - } - : {}), - ...(this.customAttributes != null - ? { - custom_attributes: this.customAttributes, - } - : {}), - }; - - const response = await this.onelogin.createUser(userData); + const response = await this.onelogin.createUser({ + $, + data: { + firstname: this.firstname, + lastname: this.lastname, + email: this.email, + username: this.username, + company: this.company, + department: this.department, + directory_id: this.directoryId, + distinguished_name: this.distinguishedName, + external_id: this.externalId, + group_id: this.groupId, + invalid_login_attempts: this.invalidLoginAttempts, + locale_code: this.localeCode, + member_of: this.memberOf, + openid_name: this.openidName, + phone: this.phone, + samaccountname: this.samaccountname, + title: this.title, + custom_attributes: parseObject(this.customAttributes), + }, + }); $.export("$summary", `Created user ${response.username} with ID ${response.id}`); return response; diff --git a/components/onelogin/common/utils.mjs b/components/onelogin/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/onelogin/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/onelogin/onelogin.app.mjs b/components/onelogin/onelogin.app.mjs index c88ee2c3f3732..b17e8b512a708 100644 --- a/components/onelogin/onelogin.app.mjs +++ b/components/onelogin/onelogin.app.mjs @@ -59,7 +59,7 @@ export default { optional: true, }, groupId: { - type: "string", + type: "integer", label: "Group ID", description: "Group to which the user belongs.", async options() { @@ -221,38 +221,34 @@ export default { }, }, methods: { - // Existing method - authKeys() { - console.log(Object.keys(this.$auth)); - }, - // Base URL _baseUrl() { - return "https://api.onelogin.com/api/1"; - }, - // Make Request - async _makeRequest(opts = {}) { - const { - $ = this, - method = "GET", - path = "/", - data, - params, - headers = {}, - ...otherOpts - } = opts; - return axios($, { - method, + return `https://${this.$auth.subdomain}.onelogin.com/api/1`; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + const config = { url: this._baseUrl() + path, - data: data, - params: params, - headers: { - ...headers, - "Authorization": `bearer:${this.$auth.access_token}`, - "Content-Type": "application/json", - }, - ...otherOpts, + headers: this._headers(), + ...opts, + }; + console.log("config: ", config); + return axios($, config); + }, + createUser(data, opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/users", + data, + ...opts, }); }, + // Pagination Logic async paginate(fn, ...args) { const results = []; @@ -275,45 +271,24 @@ export default { }, // List Groups async listGroups(opts = {}) { - return this.paginate(async ({ page }) => { - const response = await this._makeRequest({ - path: "/groups", - params: { - limit: 100, - page, - ...opts.params, - }, - }); - return response; - }, opts); + return this._makeRequest({ + path: "/groups", + ...opts, + }); }, // List Roles async listRoles(opts = {}) { - return this.paginate(async ({ page }) => { - const response = await this._makeRequest({ - path: "/roles", - params: { - limit: 100, - page, - ...opts.params, - }, - }); - return response; - }, opts); + return this._makeRequest({ + path: "/roles", + ...opts, + }); }, // List Users async listUsers(opts = {}) { - return this.paginate(async ({ page }) => { - const response = await this._makeRequest({ - path: "/users", - params: { - limit: 100, - page, - ...opts.params, - }, - }); - return response; - }, opts); + return this._makeRequest({ + path: "/users", + ...opts, + }); }, // List Directories async listDirectories(opts = {}) { @@ -329,15 +304,6 @@ export default { return response; }, opts); }, - // Create User - async createUser(data, opts = {}) { - return this._makeRequest({ - method: "POST", - path: "/users", - data, - ...opts, - }); - }, // Update User async updateUser(userId, data, opts = {}) { return this._makeRequest({ @@ -355,21 +321,5 @@ export default { ...opts, }); }, - // Emit User Created Event - async emitUserCreatedEvent(filters) { - // Implementation to emit event based on filters - // This would typically involve setting up a webhook or listening to API events - }, - // Emit Login Attempt Event - async emitLoginAttemptEvent(filters) { - // Implementation to emit event based on filters - // This would typically involve setting up a webhook or listening to API events - }, - // Emit Directory Sync Event - async emitDirectorySyncEvent(filters) { - // Implementation to emit event based on filters - // This would typically involve setting up a webhook or listening to API events - }, }, - version: "0.0.ts", }; From 4d54b6e2b016ec28872db9ea9fb97e249698c259 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 14 Feb 2025 11:43:57 -0300 Subject: [PATCH 3/6] pnpm update --- pnpm-lock.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83d4926f198d0..98ff93acf9b7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -774,8 +774,7 @@ importers: specifier: ^2.3.3 version: 2.3.3 - components/applicantstack: - specifiers: {} + components/applicantstack: {} components/appointedd: dependencies: @@ -3613,8 +3612,7 @@ importers: specifier: ^1.2.0 version: 1.6.6 - components/felt: - specifiers: {} + components/felt: {} components/fibery: dependencies: @@ -7585,8 +7583,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/ory: - specifiers: {} + components/ory: {} components/osu: {} @@ -12419,8 +12416,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/zep: - specifiers: {} + components/zep: {} components/zerobounce: dependencies: From f41b9e8f6f7aa3051bbb9db2ee3d3a6e28dfbfcc Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 21 Jul 2025 15:18:31 -0300 Subject: [PATCH 4/6] [Components] onelogin #15147 Sources - New User Created - New Login Attempt - New Directory Sync Actions - Create User - Update User - Revoke User Session --- .../actions/create-user/create-user.mjs | 192 ++++++-- .../revoke-user-sessions.mjs | 19 +- .../actions/update-user/update-user.mjs | 216 ++++++--- components/onelogin/common/constants.mjs | 114 +++++ components/onelogin/onelogin.app.mjs | 445 +++++++++--------- components/onelogin/package.json | 5 +- components/onelogin/sources/common/base.mjs | 61 +++ .../new-directory-sync-event.mjs | 106 +---- .../new-directory-sync-event/test-event.mjs | 37 ++ .../new-login-attempt/new-login-attempt.mjs | 124 +---- .../sources/new-login-attempt/test-event.mjs | 37 ++ .../onelogin/sources/new-user/new-user.mjs | 104 +--- .../onelogin/sources/new-user/test-event.mjs | 37 ++ 13 files changed, 845 insertions(+), 652 deletions(-) create mode 100644 components/onelogin/common/constants.mjs create mode 100644 components/onelogin/sources/common/base.mjs create mode 100644 components/onelogin/sources/new-directory-sync-event/test-event.mjs create mode 100644 components/onelogin/sources/new-login-attempt/test-event.mjs create mode 100644 components/onelogin/sources/new-user/test-event.mjs diff --git a/components/onelogin/actions/create-user/create-user.mjs b/components/onelogin/actions/create-user/create-user.mjs index 5847e19354679..7d6acd63cabec 100644 --- a/components/onelogin/actions/create-user/create-user.mjs +++ b/components/onelogin/actions/create-user/create-user.mjs @@ -5,69 +5,92 @@ import onelogin from "../../onelogin.app.mjs"; export default { key: "onelogin-create-user", name: "Create User", - description: "Creates a new user in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/1/users/create-user)", - version: "0.0.{{ts}}", + description: "Create a new user in OneLogin with details. [See the documentation](https://developers.onelogin.com/api-docs/2/users/create-user)", + version: "0.0.1", type: "action", props: { onelogin, + username: { + propDefinition: [ + onelogin, + "username", + ], + optional: true, + }, + email: { + propDefinition: [ + onelogin, + "email", + ], + optional: true, + }, firstname: { propDefinition: [ onelogin, "firstname", ], + optional: true, }, lastname: { propDefinition: [ onelogin, "lastname", ], + optional: true, }, - email: { + password: { propDefinition: [ onelogin, - "email", + "password", ], optional: true, }, - username: { + passwordConfirmation: { propDefinition: [ onelogin, - "username", + "passwordConfirmation", ], optional: true, }, - company: { + passwordAlgorithm: { propDefinition: [ onelogin, - "company", + "passwordAlgorithm", ], optional: true, }, - department: { + salt: { propDefinition: [ onelogin, - "department", + "salt", ], optional: true, }, - directoryId: { + title: { propDefinition: [ onelogin, - "directoryId", + "title", ], optional: true, }, - distinguishedName: { + department: { propDefinition: [ onelogin, - "distinguishedName", + "department", ], optional: true, }, - externalId: { + company: { propDefinition: [ onelogin, - "externalId", + "company", + ], + optional: true, + }, + comment: { + propDefinition: [ + onelogin, + "comment", ], optional: true, }, @@ -78,39 +101,55 @@ export default { ], optional: true, }, - invalidLoginAttempts: { + roleIds: { propDefinition: [ onelogin, - "invalidLoginAttempts", + "roleIds", ], optional: true, }, - localeCode: { + phone: { propDefinition: [ onelogin, - "localeCode", + "phone", ], optional: true, }, - memberOf: { + state: { propDefinition: [ onelogin, - "memberOf", + "state", ], optional: true, }, - openidName: { + status: { propDefinition: [ onelogin, - "openidName", + "status", ], optional: true, }, - phone: { + directoryId: { propDefinition: [ onelogin, - "phone", + "directoryId", + ], + optional: true, + }, + trustedIdpId: { + propDefinition: [ + onelogin, + "trustedIdpId", + ], + optional: true, + }, + managerUserId: { + propDefinition: [ + onelogin, + "userId", ], + label: "Manager User ID", + description: "The OneLogin User ID for the user's manager", optional: true, }, samaccountname: { @@ -120,10 +159,52 @@ export default { ], optional: true, }, - title: { + memberOf: { propDefinition: [ onelogin, - "title", + "memberOf", + ], + optional: true, + }, + userPrincipalName: { + propDefinition: [ + onelogin, + "userPrincipalName", + ], + optional: true, + }, + distinguishedName: { + propDefinition: [ + onelogin, + "distinguishedName", + ], + optional: true, + }, + externalId: { + propDefinition: [ + onelogin, + "externalId", + ], + optional: true, + }, + openidName: { + propDefinition: [ + onelogin, + "openidName", + ], + optional: true, + }, + invalidLoginAttempts: { + propDefinition: [ + onelogin, + "invalidLoginAttempts", + ], + optional: true, + }, + preferredLocaleCode: { + propDefinition: [ + onelogin, + "preferredLocaleCode", ], optional: true, }, @@ -134,37 +215,66 @@ export default { ], optional: true, }, + mappings: { + propDefinition: [ + onelogin, + "mappings", + ], + optional: true, + }, + validatePolicy: { + propDefinition: [ + onelogin, + "validatePolicy", + ], + optional: true, + }, }, async run({ $ }) { if (!this.email && !this.username) { - throw new ConfigurationError("Either email or username must be provided."); + throw new ConfigurationError("You must provide at least `Email` or `Username` property."); } const response = await this.onelogin.createUser({ $, data: { - firstname: this.firstname, - lastname: this.lastname, email: this.email, username: this.username, - company: this.company, + firstname: this.firstname, + lastname: this.lastname, + password: this.password, + password_confirmation: this.passwordConfirmation, + password_algorithm: this.passwordAlgorithm, + salt: this.salt, + title: this.title, department: this.department, + company: this.company, + comment: this.comment, + group_id: this.groupId, + role_ids: parseObject(this.roleIds), + phone: this.phone, + state: this.state && parseInt(this.state), + status: this.status && parseInt(this.status), directory_id: this.directoryId, + trusted_idp_id: this.trustedIdpId, + manager_user_id: this.managerUserId, + samaccountname: this.samaccountname, + member_of: this.memberOf, + userprincipalname: this.userPrincipalName, distinguished_name: this.distinguishedName, external_id: this.externalId, - group_id: this.groupId, - invalid_login_attempts: this.invalidLoginAttempts, - locale_code: this.localeCode, - member_of: this.memberOf, openid_name: this.openidName, - phone: this.phone, - samaccountname: this.samaccountname, - title: this.title, - custom_attributes: parseObject(this.customAttributes), + invalid_login_attempts: this.invalidLoginAttempts, + preferred_locale_code: this.preferredLocaleCode, + custom_attributes: this.customAttributes, + }, + params: { + mappings: this.mappings, + validate_policy: this.validatePolicy, }, }); - $.export("$summary", `Created user ${response.username} with ID ${response.id}`); + $.export("$summary", `Successfully created user with ID: ${response.id}`); return response; }, }; diff --git a/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs b/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs index 0f71ad5fc9272..d05e2503eabee 100644 --- a/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs +++ b/components/onelogin/actions/revoke-user-sessions/revoke-user-sessions.mjs @@ -3,21 +3,28 @@ import onelogin from "../../onelogin.app.mjs"; export default { key: "onelogin-revoke-user-sessions", name: "Revoke User Sessions", - description: "Revokes all active sessions for a specified user in OneLogin. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Revoke all active sessions for a specified user in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/2/users/update-user)", + version: "0.0.1", type: "action", props: { onelogin, - revokeUserId: { + userId: { propDefinition: [ onelogin, - "revokeUserId", + "userId", ], }, }, async run({ $ }) { - const response = await this.onelogin.revokeUserSessions(this.revokeUserId); - $.export("$summary", `Successfully revoked sessions for user ID ${this.revokeUserId}`); + const response = await this.onelogin.updateUser({ + $, + userId: this.userId, + data: { + status: 0, + }, + }); + + $.export("$summary", `Successfully revoked all sessions for user with ID: ${this.userId}`); return response; }, }; diff --git a/components/onelogin/actions/update-user/update-user.mjs b/components/onelogin/actions/update-user/update-user.mjs index bd86bf0a781b1..41e6be016d6f8 100644 --- a/components/onelogin/actions/update-user/update-user.mjs +++ b/components/onelogin/actions/update-user/update-user.mjs @@ -1,20 +1,36 @@ +import { parseObject } from "../../common/utils.mjs"; import onelogin from "../../onelogin.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "onelogin-update-user", name: "Update User", - description: "Updates an existing user's details in OneLogin. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Update an existing user's details in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/2/users/update-user)", + version: "0.0.1", type: "action", props: { onelogin, - updateUserId: { + userId: { propDefinition: [ onelogin, - "updateUserId", + "userId", ], }, + username: { + propDefinition: [ + onelogin, + "username", + ], + description: "The user's username", + optional: true, + }, + email: { + propDefinition: [ + onelogin, + "email", + ], + description: "The user's email address", + optional: true, + }, firstname: { propDefinition: [ onelogin, @@ -29,52 +45,59 @@ export default { ], optional: true, }, - email: { + password: { propDefinition: [ onelogin, - "email", + "password", ], optional: true, }, - username: { + passwordConfirmation: { propDefinition: [ onelogin, - "username", + "passwordConfirmation", ], optional: true, }, - company: { + passwordAlgorithm: { propDefinition: [ onelogin, - "company", + "passwordAlgorithm", ], optional: true, }, - department: { + salt: { propDefinition: [ onelogin, - "department", + "salt", ], optional: true, }, - directoryId: { + title: { propDefinition: [ onelogin, - "directoryId", + "title", ], optional: true, }, - distinguishedName: { + department: { propDefinition: [ onelogin, - "distinguishedName", + "department", ], optional: true, }, - externalId: { + company: { propDefinition: [ onelogin, - "externalId", + "company", + ], + optional: true, + }, + comment: { + propDefinition: [ + onelogin, + "comment", ], optional: true, }, @@ -85,41 +108,57 @@ export default { ], optional: true, }, - invalidLoginAttempts: { + roleIds: { propDefinition: [ onelogin, - "invalidLoginAttempts", + "roleIds", ], optional: true, }, - localeCode: { + phone: { propDefinition: [ onelogin, - "localeCode", + "phone", ], optional: true, }, - memberOf: { + state: { propDefinition: [ onelogin, - "memberOf", + "state", ], optional: true, }, - openidName: { + status: { propDefinition: [ onelogin, - "openidName", + "status", ], optional: true, }, - phone: { + directoryId: { propDefinition: [ onelogin, - "phone", + "directoryId", ], optional: true, }, + trustedIdpId: { + propDefinition: [ + onelogin, + "trustedIdpId", + ], + optional: true, + }, + managerUserId: { + propDefinition: [ + onelogin, + "userId", + ], + label: "Manager User ID", + description: "The OneLogin User ID for the user's manager", + optional: true, + }, samaccountname: { propDefinition: [ onelogin, @@ -127,98 +166,119 @@ export default { ], optional: true, }, - title: { + memberOf: { propDefinition: [ onelogin, - "title", + "memberOf", ], optional: true, }, - customAttributes: { + userPrincipalName: { propDefinition: [ onelogin, - "customAttributes", + "userPrincipalName", ], optional: true, }, - state: { + distinguishedName: { propDefinition: [ onelogin, - "state", + "distinguishedName", ], optional: true, }, - status: { + externalId: { propDefinition: [ onelogin, - "status", + "externalId", ], optional: true, }, - userprincipalname: { + openidName: { propDefinition: [ onelogin, - "userprincipalname", + "openidName", ], optional: true, }, - managerAdId: { + invalidLoginAttempts: { propDefinition: [ onelogin, - "manager_ad_id", + "invalidLoginAttempts", ], optional: true, }, - managerUserId: { + preferredLocaleCode: { + propDefinition: [ + onelogin, + "preferredLocaleCode", + ], + optional: true, + }, + customAttributes: { + propDefinition: [ + onelogin, + "customAttributes", + ], + optional: true, + }, + mappings: { propDefinition: [ onelogin, - "manager_user_id", + "mappings", ], optional: true, }, - roleId: { + validatePolicy: { propDefinition: [ onelogin, - "role_id", + "validatePolicy", ], optional: true, }, }, async run({ $ }) { - const userId = this.updateUserId; - const data = {}; - - if (this.firstname) data.firstname = this.firstname; - if (this.lastname) data.lastname = this.lastname; - if (this.email) data.email = this.email; - if (this.username) data.username = this.username; - if (this.company) data.company = this.company; - if (this.department) data.department = this.department; - if (this.directoryId !== undefined) data.directory_id = this.directoryId; - if (this.distinguishedName) data.distinguished_name = this.distinguishedName; - if (this.externalId) data.external_id = this.externalId; - if (this.groupId) data.group_id = parseInt(this.groupId, 10); - if (this.invalidLoginAttempts !== undefined) data.invalid_login_attempts = this.invalidLoginAttempts; - if (this.localeCode) data.locale_code = this.localeCode; - if (this.memberOf) data.member_of = this.memberOf; - if (this.openidName) data.openid_name = this.openidName; - if (this.phone) data.phone = this.phone; - if (this.samaccountname) data.samaccountname = this.samaccountname; - if (this.title) data.title = this.title; - if (this.customAttributes) data.custom_attributes = this.customAttributes; - if (this.state !== undefined) data.state = this.state; - if (this.status !== undefined) data.status = this.status; - if (this.userprincipalname) data.userprincipalname = this.userprincipalname; - if (this.managerAdId) data.manager_ad_id = this.managerAdId; - if (this.managerUserId !== undefined) data.manager_user_id = this.managerUserId; - if (this.roleId) { - data.role_id = Array.isArray(this.roleId) - ? this.roleId.map((id) => parseInt(id, 10)) - : parseInt(this.roleId, 10); - } + const response = await this.onelogin.updateUser({ + $, + userId: this.userId, + data: { + email: this.email, + username: this.username, + firstname: this.firstname, + lastname: this.lastname, + password: this.password, + password_confirmation: this.passwordConfirmation, + password_algorithm: this.passwordAlgorithm, + salt: this.salt, + title: this.title, + department: this.department, + company: this.company, + comment: this.comment, + group_id: this.groupId, + role_ids: parseObject(this.roleIds), + phone: this.phone, + state: this.state && parseInt(this.state), + status: this.status && parseInt(this.status), + directory_id: this.directoryId, + trusted_idp_id: this.trustedIdpId, + manager_user_id: this.managerUserId, + samaccountname: this.samaccountname, + member_of: this.memberOf, + userprincipalname: this.userPrincipalName, + distinguished_name: this.distinguishedName, + external_id: this.externalId, + openid_name: this.openidName, + invalid_login_attempts: this.invalidLoginAttempts, + preferred_locale_code: this.preferredLocaleCode, + custom_attributes: this.customAttributes, + }, + params: { + mappings: this.mappings, + validate_policy: this.validatePolicy, + }, + }); - const response = await this.onelogin.updateUser(userId, data); - $.export("$summary", `Successfully updated user with ID ${userId}`); + $.export("$summary", `Successfully updated user with ID: ${this.userId}`); return response; }, }; diff --git a/components/onelogin/common/constants.mjs b/components/onelogin/common/constants.mjs new file mode 100644 index 0000000000000..619c41bb003b8 --- /dev/null +++ b/components/onelogin/common/constants.mjs @@ -0,0 +1,114 @@ +export const MAPPINGS_OPTIONS = [ + { + label: "Async - Mappings run after the API returns a response", + value: "async", + }, + { + label: "Sync - Mappings run before the API returns a response", + value: "sync", + }, + { + label: "Disabled - Mappings don't run for this user", + value: "disabled", + }, +]; + +export const PASSWORD_ALGORITHM_OPTIONS = [ + "salt+sha256", + "sha256+salt", + "bcrypt", +]; + +export const STATE_OPTIONS = [ + { + label: "Unapproved", + value: "0", + }, + { + label: "Approved", + value: "1", + }, + { + label: "Rejected", + value: "2", + }, + { + label: "Unlicensed", + value: "3", + }, +]; + +export const STATUS_OPTIONS = [ + { + label: "Unactivated", + value: "0", + }, + { + label: "Active", + value: "1", + }, + { + label: "Suspended", + value: "2", + }, + { + label: "Locked", + value: "3", + }, + { + label: "Password Expired", + value: "4", + }, + { + label: "Awaiting Password Reset", + value: "5", + }, + { + label: "Password Pending", + value: "7", + }, + { + label: "Security Questions Required", + value: "8", + }, +]; + +export const EVENT_TYPES = { + USER_CREATED: 1, + LOGIN_ATTEMPT: 2, + DIRECTORY_SYNC: 3, +}; + +export const LOGIN_STATUS_OPTIONS = [ + { + label: "All", + value: "", + }, + { + label: "Successful", + value: "success", + }, + { + label: "Failed", + value: "failed", + }, +]; + +export const SYNC_STATUS_OPTIONS = [ + { + label: "All", + value: "", + }, + { + label: "Success", + value: "success", + }, + { + label: "Failed", + value: "failed", + }, + { + label: "In Progress", + value: "in_progress", + }, +]; diff --git a/components/onelogin/onelogin.app.mjs b/components/onelogin/onelogin.app.mjs index b17e8b512a708..16fc48580a496 100644 --- a/components/onelogin/onelogin.app.mjs +++ b/components/onelogin/onelogin.app.mjs @@ -1,228 +1,236 @@ import { axios } from "@pipedream/platform"; +import { + MAPPINGS_OPTIONS, + PASSWORD_ALGORITHM_OPTIONS, + STATE_OPTIONS, + STATUS_OPTIONS, +} from "./common/constants.mjs"; export default { type: "app", app: "onelogin", propDefinitions: { - // Required props for creating a new user - firstname: { + groupId: { + type: "integer", + label: "Group ID", + description: "The ID of the Group in OneLogin that the user will be assigned to", + async options({ prevContext }) { + const { + data, pagination, + } = await this.listGroups({ + params: { + after_cursor: prevContext?.nextCursor, + }, + }); + + return { + options: data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })), + context: { + nextCursor: pagination?.after_cursor, + }, + }; + }, + }, + roleIds: { + type: "integer[]", + label: "Role IDs", + description: "A list of OneLogin Role IDs the user will be assigned to", + async options({ prevContext }) { + const { + data, pagination, + } = await this.listRoles({ + params: { + after_cursor: prevContext?.nextCursor, + }, + }); + + return { + options: data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })), + context: { + nextCursor: pagination?.after_cursor, + }, + }; + }, + }, + userId: { type: "string", - label: "First Name", - description: "User's first name.", + label: "User ID", + description: "The ID of the user", + async options({ page }) { + const data = await this.listUsers({ + params: { + page: page + 1, + }, + }); + + return data.map(({ + id: value, username, email, + }) => ({ + label: `${username || email}`, + value, + })); + }, }, - lastname: { + username: { type: "string", - label: "Last Name", - description: "User's last name.", + label: "Username", + description: "The user's username (required if email is not provided)", }, email: { type: "string", label: "Email", - description: "User's email address.", - optional: true, + description: "The user's email address (required if username is not provided)", }, - username: { + firstname: { type: "string", - label: "Username", - description: "User's username.", - optional: true, + label: "First Name", + description: "The user's first name", }, - // Optional props for creating/updating a user - company: { + lastname: { type: "string", - label: "Company", - description: "Company the user is associated with.", - optional: true, + label: "Last Name", + description: "The user's last name", }, - department: { + password: { type: "string", - label: "Department", - description: "Department the user works in.", - optional: true, + label: "Password", + description: "The password to set for the user", + secret: true, }, - directoryId: { - type: "integer", - label: "Directory ID", - description: "ID of the directory (e.g., Active Directory, LDAP) from which the user was created.", - optional: true, - }, - distinguishedName: { + passwordConfirmation: { type: "string", - label: "Distinguished Name", - description: "Synchronized from Active Directory.", - optional: true, + label: "Password Confirmation", + description: "Required if the password is being set", + secret: true, }, - externalId: { + passwordAlgorithm: { type: "string", - label: "External ID", - description: "External ID that can be used to uniquely identify the user in another system.", - optional: true, + label: "Password Algorithm", + description: "Use this when importing a password that's already hashed. [See the documentation](https://developers.onelogin.com/api-docs/2/users/create-user) for further information", + options: PASSWORD_ALGORITHM_OPTIONS, }, - groupId: { - type: "integer", - label: "Group ID", - description: "Group to which the user belongs.", - async options() { - const groups = await this.listGroups(); - return groups.map((group) => ({ - label: group.name, - value: group.id.toString(), - })); - }, - optional: true, + salt: { + type: "string", + label: "Salt", + description: "The salt value used with the `Password Algorithm`", }, - invalidLoginAttempts: { - type: "integer", - label: "Invalid Login Attempts", - description: "Number of sequential invalid login attempts the user has made.", - optional: true, + title: { + type: "string", + label: "Title", + description: "The user's job title", }, - localeCode: { + department: { type: "string", - label: "Locale Code", - description: "Locale code representing a geographical, political, or cultural region.", - optional: true, + label: "Department", + description: "The user's department", }, - memberOf: { + company: { type: "string", - label: "Member Of", - description: "Groups the user is a member of.", - optional: true, + label: "Company", + description: "The user's company", }, - openidName: { + comment: { type: "string", - label: "OpenID Name", - description: "OpenID URL that can be configured in other applications that accept OpenID for sign-in.", - optional: true, + label: "Comment", + description: "Free text related to the user", }, phone: { type: "string", label: "Phone", - description: "User's phone number.", - optional: true, + description: "The [E.164](https://en.wikipedia.org/wiki/E.164) format phone number for a user", }, - samaccountname: { + state: { type: "string", - label: "SAMAccountName", - description: "Synchronized from Active Directory.", - optional: true, + label: "State", + description: "The user's state", + options: STATE_OPTIONS, }, - title: { + status: { type: "string", - label: "Title", - description: "User's title.", - optional: true, + label: "Status", + description: "The user's status", + options: STATUS_OPTIONS, }, - customAttributes: { - type: "object", - label: "Custom Attributes", - description: "Custom attributes for the user.", - optional: true, + directoryId: { + type: "integer", + label: "Directory ID", + description: "The ID of the OneLogin Directory the user will be assigned to", }, - // Props for updating an existing user - updateUserId: { + trustedIdpId: { + type: "integer", + label: "Trusted IDP ID", + description: "The ID of the OneLogin Trusted IDP the user will be assigned to", + }, + samaccountname: { type: "string", - label: "User ID", - description: "Unique ID of the user to update.", - async options() { - const users = await this.listUsers(); - return users.map((user) => ({ - label: `${user.firstname} ${user.lastname} (${user.email})`, - value: user.id.toString(), - })); - }, + label: "SAM Account Name", + description: "The user's Active Directory username", }, - // Props for revoking user sessions - revokeUserId: { + memberOf: { type: "string", - label: "User ID", - description: "Unique ID of the user to revoke sessions for.", - async options() { - const users = await this.listUsers(); - return users.map((user) => ({ - label: `${user.firstname} ${user.lastname} (${user.email})`, - value: user.id.toString(), - })); - }, + label: "Member Of", + description: "The user's directory membership", }, - // Props for User Created Event Trigger filters - filterUserRole: { + userPrincipalName: { type: "string", - label: "User Role", - description: "Filter by user role.", - async options() { - const roles = await this.listRoles(); - return roles.map((role) => ({ - label: role.name, - value: role.id.toString(), - })); - }, - optional: true, + label: "User Principal Name", + description: "The principle name of the user", }, - filterGroup: { + distinguishedName: { type: "string", - label: "Group", - description: "Filter by user group.", - async options() { - const groups = await this.listGroups(); - return groups.map((group) => ({ - label: group.name, - value: group.id.toString(), - })); - }, - optional: true, + label: "Distinguished Name", + description: "The distinguished name of the user", }, - // Props for Login Attempt Event Trigger filters - filterLoginSuccess: { - type: "boolean", - label: "Successful Attempts", - description: "Filter to only include successful login attempts.", - optional: true, + externalId: { + type: "string", + label: "External ID", + description: "The ID of the user in an external directory", }, - filterLoginFailure: { - type: "boolean", - label: "Failed Attempts", - description: "Filter to only include failed login attempts.", - optional: true, + openidName: { + type: "string", + label: "OpenID Name", + description: "The name configured for use in other applications that accept OpenID for sign-in", }, - // Props for Directory Sync Event Trigger filters - directoryName: { + invalidLoginAttempts: { + type: "integer", + label: "Invalid Login Attempts", + description: "The number of sequential invalid login attempts the user has made", + }, + preferredLocaleCode: { type: "string", - label: "Directory Name", - description: "Filter by specific directory name.", - async options() { - const directories = await this.listDirectories(); - return directories.map((dir) => ({ - label: dir.name, - value: dir.id.toString(), - })); - }, - optional: true, + label: "Preferred Locale Code", + description: "The 2-character language locale for the user, such as `en` for English or `es` for Spanish.", }, - syncStatus: { + customAttributes: { + type: "object", + label: "Custom Attributes", + description: "An object to contain any other custom attributes you have configured", + }, + mappings: { type: "string", - label: "Sync Status", - description: "Filter by sync status.", - options: [ - { - label: "Success", - value: "success", - }, - { - label: "Failure", - value: "failure", - }, - { - label: "In Progress", - value: "in_progress", - }, - ], - optional: true, + label: "Mappings", + description: "Controls how mappings will be applied to the user on creation.", + options: MAPPINGS_OPTIONS, + }, + validatePolicy: { + type: "boolean", + label: "Validate Policy", + description: "Will passwords validate against the User Policy?", }, }, methods: { _baseUrl() { - return `https://${this.$auth.subdomain}.onelogin.com/api/1`; + return `https://${this.$auth.subdomain}.onelogin.com`; }, _headers() { return { @@ -232,94 +240,77 @@ export default { _makeRequest({ $ = this, path, ...opts }) { - const config = { + return axios($, { url: this._baseUrl() + path, headers: this._headers(), ...opts, - }; - console.log("config: ", config); - return axios($, config); + }); }, - createUser(data, opts = {}) { + listGroups(opts = {}) { return this._makeRequest({ - method: "POST", - path: "/users", - data, + path: "/api/1/groups", ...opts, }); }, - - // Pagination Logic - async paginate(fn, ...args) { - const results = []; - let page = 1; - let hasNext = true; - while (hasNext) { - const response = await fn({ - page, - ...args, - }); - if (Array.isArray(response)) { - results.push(...response); - hasNext = false; - } else { - results.push(...response); - hasNext = false; - } - } - return results; - }, - // List Groups - async listGroups(opts = {}) { + listRoles(opts = {}) { return this._makeRequest({ - path: "/groups", + path: "/api/1/roles", ...opts, }); }, - // List Roles - async listRoles(opts = {}) { + listUsers(opts = {}) { return this._makeRequest({ - path: "/roles", + path: "/api/2/users", ...opts, }); }, - // List Users - async listUsers(opts = {}) { + createUser(opts = {}) { return this._makeRequest({ - path: "/users", + method: "POST", + path: "/api/2/users", ...opts, }); }, - // List Directories - async listDirectories(opts = {}) { - return this.paginate(async ({ page }) => { - const response = await this._makeRequest({ - path: "/directories", - params: { - limit: 100, - page, - ...opts.params, - }, - }); - return response; - }, opts); - }, - // Update User - async updateUser(userId, data, opts = {}) { + updateUser({ + userId, ...opts + }) { return this._makeRequest({ method: "PUT", - path: `/users/${userId}`, - data, + path: `/api/2/users/${userId}`, ...opts, }); }, - // Revoke User Sessions - async revokeUserSessions(userId, opts = {}) { + listEvents(opts = {}) { return this._makeRequest({ - method: "POST", - path: `/users/${userId}/logout`, + path: "/api/1/events", ...opts, }); }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let count = 0; + let nextCursor = null; + + do { + params.after_cursor = nextCursor; + const { + data, + pagination: { after_cursor }, + } = await fn({ + params, + ...opts, + }); + for (const d of data) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + nextCursor = after_cursor; + } while (nextCursor); + }, }, }; diff --git a/components/onelogin/package.json b/components/onelogin/package.json index 0945197d47fa2..e7b36a0463c9d 100644 --- a/components/onelogin/package.json +++ b/components/onelogin/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/onelogin", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream OneLogin Components", "main": "onelogin.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } } diff --git a/components/onelogin/sources/common/base.mjs b/components/onelogin/sources/common/base.mjs new file mode 100644 index 0000000000000..1f5d0d2c95d46 --- /dev/null +++ b/components/onelogin/sources/common/base.mjs @@ -0,0 +1,61 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import onelogin from "../../onelogin.app.mjs"; + +export default { + props: { + onelogin, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || "1970-01-01T00:00:00Z"; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + + const response = this.onelogin.paginate({ + fn: this.onelogin.listEvents, + maxResults, + params: { + since: lastDate, + sort: "-created_at", + event_type_id: this.getEventType(), + }, + }); + + let responseArray = []; + for await (const item of response) { + responseArray.push(item); + } + + if (responseArray.length) { + this._setLastDate(responseArray[0].created_at); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.created_at), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs b/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs index 762de5d847b10..fc1644dc306a9 100644 --- a/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs +++ b/components/onelogin/sources/new-directory-sync-event/new-directory-sync-event.mjs @@ -1,104 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import onelogin from "../../onelogin.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "onelogin-new-directory-sync-event", name: "New Directory Sync Event", - description: "Emit new event when a directory sync event is triggered in OneLogin. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event when a directory sync event is triggered in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/1/events/get-events)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - onelogin, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - directoryName: { - propDefinition: [ - onelogin, - "directoryName", - ], - optional: true, - }, - syncStatus: { - propDefinition: [ - onelogin, - "syncStatus", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - const events = await this.runFetchEvents(); - const recentEvents = events.slice(-50).reverse(); - for (const event of recentEvents) { - this.$emit( - event, - { - id: event.id.toString(), - summary: `Directory Sync Event #${event.id}`, - ts: Date.parse(event.created_at), - }, - ); - } - const lastEvent = recentEvents[recentEvents.length - 1]; - if (lastEvent) { - await this.db.set("last_cursor", lastEvent.id); - } - }, - async activate() { - // No webhook setup required for polling source - }, - async deactivate() { - // No webhook teardown required for polling source - }, - }, methods: { - async runFetchEvents() { - const lastCursor = await this.db.get("last_cursor"); - const params = { - event_type_id: 13, - limit: 50, - sort: "-id", - }; - if (lastCursor) { - params.after_cursor = lastCursor; - } - if (this.directoryName) { - params.directory_id = this.directoryName; - } - if (this.syncStatus) { - params.resolution = this.syncStatus; - } - const response = await this.onelogin._makeRequest({ - path: "/events", - params, - }); - return response; + ...common.methods, + getEventType() { + return 117; + }, + getSummary(item) { + return `Directory sync event: ${item.directory_sync_run_id}`; }, }, - async run() { - const events = await this.runFetchEvents(); - for (const event of events) { - this.$emit( - event, - { - id: event.id.toString(), - summary: `Directory Sync Event #${event.id}`, - ts: Date.parse(event.created_at), - }, - ); - } - if (events.length > 0) { - const lastEvent = events[events.length - 1]; - await this.db.set("last_cursor", lastEvent.id); - } - }, + sampleEmit, }; diff --git a/components/onelogin/sources/new-directory-sync-event/test-event.mjs b/components/onelogin/sources/new-directory-sync-event/test-event.mjs new file mode 100644 index 0000000000000..fdcb45e3921f3 --- /dev/null +++ b/components/onelogin/sources/new-directory-sync-event/test-event.mjs @@ -0,0 +1,37 @@ +export default { + "id": 999999999, + "created_at": "2014-12-19T02:02:39.276Z", + "account_id": 55555, + "user_id": 88888888, + "event_type_id": 117, + "notes": null, + "ipaddr": "11.111.11.111", + "actor_user_id": 7777777, + "assuming_acting_user_id": null, + "role_id": null, + "app_id": null, + "group_id": null, + "otp_device_id": null, + "policy_id": null, + "actor_system": "", + "custom_message": null, + "role_name": null, + "app_name": null, + "group_name": null, + "actor_user_name": "Xavier Wong", + "user_name": "Xavier Wong", + "policy_name": null, + "otp_device_name": null, + "operation_name": null, + "directory_sync_run_id": 123, + "directory_id": null, + "resolution": null, + "client_id": null, + "resource_type_id": null, + "error_description": null, + "proxy_ip": null, + "risk_score": null, + "risk_reasons": null, + "risk_cookie_id": null, + "browser_fingerprint": null +} \ No newline at end of file diff --git a/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs b/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs index f6e5f887e46e0..127397235a988 100644 --- a/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs +++ b/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs @@ -1,124 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import onelogin from "../../onelogin.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "onelogin-new-login-attempt", name: "New Login Attempt", - description: "Emit new event when a login attempt occurs in OneLogin. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event when a login attempt occurs in OneLogin. Users can optionally filter by successful or failed attempts. [See the documentation](https://developers.onelogin.com/api-docs/1/events/get-events)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - onelogin: { - type: "app", - app: "onelogin", - }, - db: { - type: "$.service.db", - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - filterLoginSuccess: { - propDefinition: [ - "onelogin", - "filterLoginSuccess", - ], - }, - filterLoginFailure: { - propDefinition: [ - "onelogin", - "filterLoginFailure", - ], - }, - }, - hooks: { - async deploy() { - await this.fetchAndEmitEvents(true); - }, - async activate() { - // No webhook setup required for polling source - }, - async deactivate() { - // No webhook cleanup required for polling source - }, - }, methods: { - async fetchAndEmitEvents(isDeploy = false) { - const lastCursor = await this.db.get("after_cursor"); - const params = { - limit: 50, - sort: "-created_at", - }; - - if (lastCursor) { - params.after_cursor = lastCursor; - } - - // Define event_type_ids that correspond to login attempts - // These IDs should be replaced with actual ones from OneLogin - const loginAttemptEventTypeIds = [ - 5, - 13, - ]; - params.event_type_id = loginAttemptEventTypeIds.join(","); - - const response = await this.onelogin._makeRequest({ - path: "/events", - params, - }); - - const events = response; - - // Emit events from oldest to newest - for (const event of events.reverse()) { - if (this._isLoginAttempt(event) && this._matchesFilters(event)) { - const eventId = event.id - ? event.id.toString() - : undefined; - const eventTimestamp = event.created_at - ? Date.parse(event.created_at) - : Date.now(); - const emitMeta = { - id: eventId || undefined, - summary: `Login attempt by ${event.user_name}`, - ts: eventTimestamp, - }; - this.$emit(event, emitMeta); - } - } - - // Update the cursor - if (events.length > 0 && response.pagination && response.pagination.after_cursor) { - await this.db.set("after_cursor", response.pagination.after_cursor); - } + ...common.methods, + getEventType() { + return 111; }, - _isLoginAttempt(event) { - return [ - 5, - 13, - ].includes(event.event_type_id); + getSummary(item) { + return `${item.user_name} attempted to login`; }, - _matchesFilters(event) { - let isSuccess = !event.error_description; - if (this.filterLoginSuccess && isSuccess) { - return true; - } - if (this.filterLoginFailure && !isSuccess) { - return true; - } - if (!this.filterLoginSuccess && !this.filterLoginFailure) { - return true; - } - return false; - }, - }, - async run() { - await this.fetchAndEmitEvents(); }, + sampleEmit, }; diff --git a/components/onelogin/sources/new-login-attempt/test-event.mjs b/components/onelogin/sources/new-login-attempt/test-event.mjs new file mode 100644 index 0000000000000..ac608920d5ed9 --- /dev/null +++ b/components/onelogin/sources/new-login-attempt/test-event.mjs @@ -0,0 +1,37 @@ +export default { + "id":36114558381, + "created_at":"2025-07-21T17:30:44.190Z", + "account_id":452500, + "user_id":279115532, + "event_type_id":111, + "notes":null, + "ipaddr":"98.80.221.195", + "actor_user_id":null, + "assuming_acting_user_id":null, + "role_id":null, + "app_id":null, + "group_id":null, + "otp_device_id":null, + "policy_id":null, + "actor_system":"API", + "custom_message":null, + "role_name":null, + "app_name":null, + "group_name":null, + "actor_user_name":"API", + "user_name":"Username", + "policy_name":null, + "otp_device_name":null, + "operation_name":null, + "directory_sync_run_id":null, + "directory_id":null, + "resolution":null, + "client_id":"1e87e84576a77d999e89e3ba110df9461decc2b1564bb1b9aa3e05fcbafb5918", + "resource_type_id":null, + "error_description":null, + "proxy_ip":null, + "risk_score":null, + "risk_reasons":null, + "risk_cookie_id":null, + "browser_fingerprint":null, +}; \ No newline at end of file diff --git a/components/onelogin/sources/new-user/new-user.mjs b/components/onelogin/sources/new-user/new-user.mjs index 6b6a60967cdda..c7c70fc1e9f39 100644 --- a/components/onelogin/sources/new-user/new-user.mjs +++ b/components/onelogin/sources/new-user/new-user.mjs @@ -1,102 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import onelogin from "../../onelogin.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "onelogin-new-user", name: "New User Created", - description: "Emit a new event when a user is created in OneLogin. [See the documentation]().", - version: "0.0.{{ts}}", + description: "Emit new event when a user is created in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/1/events/get-events)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - onelogin, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + methods: { + ...common.methods, + getEventType() { + return 13; }, - filterUserRole: { - propDefinition: [ - onelogin, - "filterUserRole", - ], - optional: true, + getSummary(item) { + return `New user created with ID: ${item.user_id}`; }, - filterGroup: { - propDefinition: [ - onelogin, - "filterGroup", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - const lastRun = this.db.get("lastRun"); - const now = new Date().toISOString(); - const since = lastRun || new Date(Date.now() - this.timer.intervalSeconds * 1000).toISOString(); - const params = { - event_type_id: "13", - since, - limit: 50, - sort: "-created_at", - }; - const events = await this.onelogin._makeRequest({ - path: "/events", - params, - }); - for (const event of events) { - if (this.filterUserRole && event.role_id !== parseInt(this.filterUserRole, 10)) { - continue; - } - if (this.filterGroup && event.group_id !== parseInt(this.filterGroup, 10)) { - continue; - } - this.$emit(event, { - id: event.id.toString(), - summary: `New user created: ${event.user_name}`, - ts: Date.parse(event.created_at) || Date.now(), - }); - } - this.db.set("lastRun", now); - }, - async activate() { - // No webhook setup required for polling source - }, - async deactivate() { - // No webhook cleanup required for polling source - }, - }, - async run() { - const lastRun = this.db.get("lastRun"); - const now = new Date().toISOString(); - const params = { - event_type_id: "13", - since: lastRun || new Date(Date.now() - this.timer.intervalSeconds * 1000).toISOString(), - limit: 100, - sort: "-created_at", - }; - const events = await this.onelogin._makeRequest({ - path: "/events", - params, - }); - for (const event of events) { - if (this.filterUserRole && event.role_id !== parseInt(this.filterUserRole, 10)) { - continue; - } - if (this.filterGroup && event.group_id !== parseInt(this.filterGroup, 10)) { - continue; - } - this.$emit(event, { - id: event.id.toString(), - summary: `New user created: ${event.user_name}`, - ts: Date.parse(event.created_at) || Date.now(), - }); - } - this.db.set("lastRun", now); }, + sampleEmit, }; diff --git a/components/onelogin/sources/new-user/test-event.mjs b/components/onelogin/sources/new-user/test-event.mjs new file mode 100644 index 0000000000000..8be0ea41bde98 --- /dev/null +++ b/components/onelogin/sources/new-user/test-event.mjs @@ -0,0 +1,37 @@ +export default { + "id":36114558381, + "created_at":"2025-07-21T17:30:44.190Z", + "account_id":452500, + "user_id":279115532, + "event_type_id":13, + "notes":null, + "ipaddr":"98.80.221.195", + "actor_user_id":null, + "assuming_acting_user_id":null, + "role_id":null, + "app_id":null, + "group_id":null, + "otp_device_id":null, + "policy_id":null, + "actor_system":"API", + "custom_message":null, + "role_name":null, + "app_name":null, + "group_name":null, + "actor_user_name":"API", + "user_name":"Username", + "policy_name":null, + "otp_device_name":null, + "operation_name":null, + "directory_sync_run_id":null, + "directory_id":null, + "resolution":null, + "client_id":"1e87e84576a77d999e89e3ba110df9461decc2b1564bb1b9aa3e05fcbafb5918", + "resource_type_id":null, + "error_description":null, + "proxy_ip":null, + "risk_score":null, + "risk_reasons":null, + "risk_cookie_id":null, + "browser_fingerprint":null, +}; \ No newline at end of file From 4844cc4f25d61e02ac7a6c986fa7a60193f61861 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 21 Jul 2025 15:20:07 -0300 Subject: [PATCH 5/6] pnpm update --- pnpm-lock.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b19dba778b794..606b52e255141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9347,7 +9347,11 @@ importers: components/onehash: {} - components/onelogin: {} + components/onelogin: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/onenote: dependencies: @@ -11493,8 +11497,7 @@ importers: components/rewardful: {} - components/rewiser: - specifiers: {} + components/rewiser: {} components/rex: dependencies: @@ -37258,6 +37261,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: From 53da41b9bca4fdc7e7ca7419d4eaab71ca05054c Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 23 Jul 2025 10:57:52 -0300 Subject: [PATCH 6/6] Add new event source and refactor API paths in OneLogin component - Introduced a new event source for emitting events in OneLogin. - Added event type selection with dynamic options based on available event types. - Refactored API paths in the OneLogin app to remove redundant '/api' prefix. - Implemented a utility function to convert snake_case to Title Case for better display of event types. --- components/onelogin/common/utils.mjs | 6 +++ components/onelogin/onelogin.app.mjs | 40 +++++++++++++++---- .../onelogin/sources/new-event/new-event.mjs | 32 +++++++++++++++ .../test-event.mjs | 0 .../new-login-attempt/new-login-attempt.mjs | 22 ---------- 5 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 components/onelogin/sources/new-event/new-event.mjs rename components/onelogin/sources/{new-login-attempt => new-event}/test-event.mjs (100%) delete mode 100644 components/onelogin/sources/new-login-attempt/new-login-attempt.mjs diff --git a/components/onelogin/common/utils.mjs b/components/onelogin/common/utils.mjs index dcc9cc61f6f41..94c2fdf11f26e 100644 --- a/components/onelogin/common/utils.mjs +++ b/components/onelogin/common/utils.mjs @@ -22,3 +22,9 @@ export const parseObject = (obj) => { } return obj; }; + +export const snakeCaseToTitleCase = (s) => + s.toLowerCase() + .replace(/^_*(.)|_+(.)/g, (s, c, d) => c + ? c.toUpperCase() + : " " + d.toUpperCase()); diff --git a/components/onelogin/onelogin.app.mjs b/components/onelogin/onelogin.app.mjs index 16fc48580a496..009ac7d8bff1e 100644 --- a/components/onelogin/onelogin.app.mjs +++ b/components/onelogin/onelogin.app.mjs @@ -5,6 +5,7 @@ import { STATE_OPTIONS, STATUS_OPTIONS, } from "./common/constants.mjs"; +import { snakeCaseToTitleCase } from "./common/utils.mjs"; export default { type: "app", @@ -81,6 +82,25 @@ export default { })); }, }, + eventType: { + type: "string", + label: "Event Type", + description: "The type of event to emit", + async options({ page }) { + const { data } = await this.listEventTypes({ + params: { + page, + }, + }); + + return data.map(({ + id: value, name, description, + }) => ({ + label: `${snakeCaseToTitleCase(name)}: ${description}`, + value, + })); + }, + }, username: { type: "string", label: "Username", @@ -230,7 +250,7 @@ export default { }, methods: { _baseUrl() { - return `https://${this.$auth.subdomain}.onelogin.com`; + return `https://${this.$auth.subdomain}.onelogin.com/api`; }, _headers() { return { @@ -248,26 +268,26 @@ export default { }, listGroups(opts = {}) { return this._makeRequest({ - path: "/api/1/groups", + path: "/1/groups", ...opts, }); }, listRoles(opts = {}) { return this._makeRequest({ - path: "/api/1/roles", + path: "/1/roles", ...opts, }); }, listUsers(opts = {}) { return this._makeRequest({ - path: "/api/2/users", + path: "/2/users", ...opts, }); }, createUser(opts = {}) { return this._makeRequest({ method: "POST", - path: "/api/2/users", + path: "/2/users", ...opts, }); }, @@ -276,13 +296,19 @@ export default { }) { return this._makeRequest({ method: "PUT", - path: `/api/2/users/${userId}`, + path: `/2/users/${userId}`, ...opts, }); }, listEvents(opts = {}) { return this._makeRequest({ - path: "/api/1/events", + path: "/1/events", + ...opts, + }); + }, + listEventTypes(opts = {}) { + return this._makeRequest({ + path: "/1/events/types", ...opts, }); }, diff --git a/components/onelogin/sources/new-event/new-event.mjs b/components/onelogin/sources/new-event/new-event.mjs new file mode 100644 index 0000000000000..af68457de8409 --- /dev/null +++ b/components/onelogin/sources/new-event/new-event.mjs @@ -0,0 +1,32 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "onelogin-new-event", + name: "New Event", + description: "Emit new event when a selected event occurs in OneLogin. [See the documentation](https://developers.onelogin.com/api-docs/1/events/get-events)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + eventType: { + propDefinition: [ + common.props.onelogin, + "eventType", + ], + withLabel: true, + }, + }, + methods: { + ...common.methods, + getEventType() { + return this.eventType.value; + }, + getSummary() { + return `New event: ${this.eventType.label}`; + }, + }, + sampleEmit, +}; diff --git a/components/onelogin/sources/new-login-attempt/test-event.mjs b/components/onelogin/sources/new-event/test-event.mjs similarity index 100% rename from components/onelogin/sources/new-login-attempt/test-event.mjs rename to components/onelogin/sources/new-event/test-event.mjs diff --git a/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs b/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs deleted file mode 100644 index 127397235a988..0000000000000 --- a/components/onelogin/sources/new-login-attempt/new-login-attempt.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import common from "../common/base.mjs"; -import sampleEmit from "./test-event.mjs"; - -export default { - ...common, - key: "onelogin-new-login-attempt", - name: "New Login Attempt", - description: "Emit new event when a login attempt occurs in OneLogin. Users can optionally filter by successful or failed attempts. [See the documentation](https://developers.onelogin.com/api-docs/1/events/get-events)", - version: "0.0.1", - type: "source", - dedupe: "unique", - methods: { - ...common.methods, - getEventType() { - return 111; - }, - getSummary(item) { - return `${item.user_name} attempted to login`; - }, - }, - sampleEmit, -};