Skip to content

Commit 2502369

Browse files
waleedlatif1claude
andauthored
feat(integrations): SAP S/4HANA (#4301)
* feat(integrations): SAP S/4HANA tools, block, and proxy with multi-deployment support * fix(sap_s4hana): address PR review comments - Validate baseUrl/tokenUrl in Zod schema and at runtime to prevent SSRF (https-only, deny loopback/link-local/cloud-metadata hosts) - Cap proxy token cache at 500 entries with LRU eviction - Add 30s timeout to outbound token, CSRF, and OData fetches - Make parseJsonInput return T | undefined so missing input is type-safe - Reset authType when deploymentType changes and surface OAuth fields whenever auth is not basic, so cloud_public users always see clientId/ clientSecret after switching from a basic-auth private deployment - Reject OData service names that are not uppercase identifiers and paths containing ".." or "." traversal segments Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): allow versioned service names; tighten proxy SSRF defenses - Permit ";v=NNNN" suffix on ServiceName regex so the four delivery tools (API_OUTBOUND_DELIVERY_SRV;v=0002, API_INBOUND_DELIVERY_SRV;v=0002) pass schema validation - Restrict subdomain to RFC 1123 label characters and region to lowercase alphanumeric short codes; run the constructed cloud_public host through assertSafeExternalUrl so a crafted subdomain (e.g. "evil.com#") cannot redirect requests carrying SAP credentials - Block RFC-1918 (10/8, 172.16/12, 192.168/16), 127/8, 169.254/16, and 0.0.0.0 via isPrivateIPv4, plus IPv4-mapped IPv6 variants (::ffff:10.0.0.1, ::10.0.0.1) so private internal hosts cannot be reached from baseUrl, tokenUrl, or the resolved cloud_public URL Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): catch hex-form IPv4-mapped IPv6 in SSRF check The WHATWG URL parser normalizes IPv4-mapped IPv6 addresses to hex form (e.g. [::ffff:169.254.169.254] → [::ffff:a9fe:a9fe]), which slipped past the dotted-decimal-only extractor. Decode the trailing two 16-bit hex groups back into IPv4 octets and run them through isPrivateIPv4. Also add isPrivateOrLoopbackIPv6 so pure IPv6 loopback (::, ::1), unique local addresses (fc00::/7), and link-local (fe80::/10) cannot be reached. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): scope CSRF metadata fetch and isolate token cache by secret - buildOdataUrl skips request query params when called with an internal pathOverride so the /$metadata CSRF probe never carries user OData options ($filter, $top, $select), which were causing write operations through the generic odata_query tool to fail. - tokenCacheKey now mixes a sha256 hash of clientSecret into the cache key so two tenants sharing the same tokenUrl + clientId but different secrets get isolated entries (no cross-tenant token leak). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): reject ?/# in service path; trim long update tool descriptions - ServicePath validator now rejects "?" and "#" so a caller can't smuggle query options through the path field (e.g., "/A_BusinessPartner?$format=atomsvc"); the Zod refine now reports ".." / "." segments, "?", and "#" together. - Update Customer / Update Supplier / Update Purchase Requisition tool descriptions exceeded the docs generator's 600-char regex window, so they were rendering with empty descriptions on the integrations landing page. Trimmed them to fit while keeping the limited-fields note and the If-Match guidance, then regenerated integrations.json and tool docs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): reject percent-encoded path traversal; widen Set-Cookie split - ServicePath now also rejects %2e/%2E, %2f/%2F, %5c/%5C, %3f/%3F, %23 so a caller cannot smuggle ".." / "." / "/" / "\" / "?" / "#" past the validator and have SAP's ABAP/ICM gateway decode them server-side. - joinSetCookies fallback regex now allows the ", " separator that's used when multiple Set-Cookie values are folded onto one header line (older runtimes without Headers.getSetCookie). Prevents CSRF cookies from being concatenated into a single value during write operations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): preserve $ in OData query params; reject empty items array - buildOdataUrl now constructs query strings manually with encodeURIComponent and restores literal "$" so OData system options ($filter, $top, $select, $expand, $orderby, $skip, $format) reach SAP and any intermediary proxies/WAFs as-is, not as "%24filter". URLSearchParams was percent-encoding "$" to "%24" which most ICMs decode but some intermediaries silently drop, returning unfiltered results. - create_sales_order now rejects an empty items array (matches create_purchase_requisition) so callers get a clear client-side error instead of an opaque SAP validation failure on the deep-insert. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): ignore baseUrl on cloud_public to prevent token redirection Why: resolveHost previously preferred baseUrl unconditionally. A caller sending deploymentType=cloud_public with a baseUrl pointing elsewhere would obtain a real SAP UAA token, then forward it as Bearer to the attacker host. Zod superRefine did not validate baseUrl for cloud_public. Fix: resolveHost now constructs the SAP host from subdomain when deploymentType is cloud_public and only uses baseUrl for cloud_private and on_premise (where it is already SSRF-checked in superRefine). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(icons): use useId for SapS4HanaIcon and PipedriveIcon gradients Why: hardcoded SVG gradient/mask IDs collide when an icon renders more than once on a page (e.g. integrations listing). All other icons in this file use React's useId() — these were inconsistent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * icons * fix(icons): use useId for AWS-style icon gradients Why: IAMIcon, IdentityCenterIcon, STSIcon, SESIcon, and SecretsManagerIcon all used hardcoded `id='xxxGradient'` values that collide when an icon renders more than once on a page (e.g. integrations listing). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(sap_s4hana): ignore tokenUrl on cloud_public to prevent UAA redirection Why: resolveTokenUrl previously honored caller-supplied tokenUrl regardless of deploymentType, mirroring the same redirection class as the prior baseUrl bug. A cloud_public caller could send tokenUrl to an attacker host, causing the proxy to POST clientId:clientSecret as Basic auth to it. superRefine for cloud_public did not validate tokenUrl. Fix: derive UAA URL from subdomain+region for cloud_public; only honor tokenUrl for cloud_private/on_premise (already SSRF-checked). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(icons): remove unused mask in PipedriveIcon Why: the <mask> element had no consumer (no mask='url(#...)' anywhere in the SVG), so both it and the maskId variable were dead code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8266f0a commit 2502369

51 files changed

Lines changed: 8495 additions & 30 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/components/icons.tsx

Lines changed: 52 additions & 15 deletions
Large diffs are not rendered by default.

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ import {
154154
RootlyIcon,
155155
S3Icon,
156156
SalesforceIcon,
157+
SapS4HanaIcon,
157158
SESIcon,
158159
SearchIcon,
159160
SecretsManagerIcon,
@@ -369,6 +370,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
369370
rootly: RootlyIcon,
370371
s3: S3Icon,
371372
salesforce: SalesforceIcon,
373+
sap_s4hana: SapS4HanaIcon,
372374
search: SearchIcon,
373375
secrets_manager: SecretsManagerIcon,
374376
sendgrid: SendgridIcon,

apps/docs/content/docs/en/tools/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
"rootly",
151151
"s3",
152152
"salesforce",
153+
"sap_s4hana",
153154
"search",
154155
"secrets_manager",
155156
"sendgrid",

apps/docs/content/docs/en/tools/sap_s4hana.mdx

Lines changed: 1182 additions & 0 deletions
Large diffs are not rendered by default.

apps/sim/app/(landing)/integrations/data/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ import {
154154
RootlyIcon,
155155
S3Icon,
156156
SalesforceIcon,
157+
SapS4HanaIcon,
157158
SESIcon,
158159
SearchIcon,
159160
SecretsManagerIcon,
@@ -351,6 +352,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
351352
rootly: RootlyIcon,
352353
s3: S3Icon,
353354
salesforce: SalesforceIcon,
355+
sap_s4hana: SapS4HanaIcon,
354356
search: SearchIcon,
355357
secrets_manager: SecretsManagerIcon,
356358
sendgrid: SendgridIcon,

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11379,6 +11379,173 @@
1137911379
"integrationTypes": ["crm", "customer-support", "sales"],
1138011380
"tags": ["sales-engagement", "customer-support"]
1138111381
},
11382+
{
11383+
"type": "sap_s4hana",
11384+
"slug": "sap-s-4hana",
11385+
"name": "SAP S/4HANA",
11386+
"description": "Read and write SAP S/4HANA Cloud business data via OData",
11387+
"longDescription": "Connect SAP S/4HANA Cloud Public Edition with per-tenant OAuth 2.0 client credentials configured in your Communication Arrangements. Read and create business partners, customers, suppliers, sales orders, deliveries (inbound/outbound), billing documents, products, stock and material documents, purchase requisitions, purchase orders, and supplier invoices, or run arbitrary OData v2 queries against any whitelisted Communication Scenario.",
11388+
"bgColor": "#0A6ED1",
11389+
"iconName": "SapS4HanaIcon",
11390+
"docsUrl": "https://docs.sim.ai/tools/sap_s4hana",
11391+
"operations": [
11392+
{
11393+
"name": "List Business Partners",
11394+
"description": "List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11395+
},
11396+
{
11397+
"name": "Get Business Partner",
11398+
"description": "Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner)."
11399+
},
11400+
{
11401+
"name": "Create Business Partner",
11402+
"description": "Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessPartner). For Person category 1 provide FirstName and LastName. For Organization category 2 provide OrganizationBPName1."
11403+
},
11404+
{
11405+
"name": "Update Business Partner",
11406+
"description": "Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11407+
},
11408+
{
11409+
"name": "List Customers",
11410+
"description": "List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11411+
},
11412+
{
11413+
"name": "Get Customer",
11414+
"description": "Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer)."
11415+
},
11416+
{
11417+
"name": "Update Customer",
11418+
"description": "Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Customer PATCH is limited to modifiable fields such as OrderIsBlockedForCustomer, DeliveryIsBlock, BillingIsBlockedForCustomer, PostingIsBlocked, and DeletionIndicator. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11419+
},
11420+
{
11421+
"name": "List Suppliers",
11422+
"description": "List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11423+
},
11424+
{
11425+
"name": "Get Supplier",
11426+
"description": "Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier)."
11427+
},
11428+
{
11429+
"name": "Update Supplier",
11430+
"description": "Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Supplier PATCH is limited to modifiable fields such as PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, and SupplierAccountGroup. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11431+
},
11432+
{
11433+
"name": "List Sales Orders",
11434+
"description": "List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11435+
},
11436+
{
11437+
"name": "Get Sales Order",
11438+
"description": "Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder)."
11439+
},
11440+
{
11441+
"name": "Create Sales Order",
11442+
"description": "Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) with deep insert of sales order items via to_Item."
11443+
},
11444+
{
11445+
"name": "Update Sales Order",
11446+
"description": "Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11447+
},
11448+
{
11449+
"name": "Delete Sales Order",
11450+
"description": "Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only orders without subsequent documents (deliveries, invoices) can be deleted; otherwise reject items via update instead."
11451+
},
11452+
{
11453+
"name": "List Outbound Deliveries",
11454+
"description": "List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11455+
},
11456+
{
11457+
"name": "Get Outbound Delivery",
11458+
"description": "Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=0002, A_OutbDeliveryHeader)."
11459+
},
11460+
{
11461+
"name": "List Inbound Deliveries",
11462+
"description": "List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11463+
},
11464+
{
11465+
"name": "Get Inbound Delivery",
11466+
"description": "Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, A_InbDeliveryHeader)."
11467+
},
11468+
{
11469+
"name": "List Billing Documents",
11470+
"description": "List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11471+
},
11472+
{
11473+
"name": "Get Billing Document",
11474+
"description": "Retrieve a single billing document (customer invoice) by BillingDocument key from SAP S/4HANA Cloud (API_BILLING_DOCUMENT_SRV, A_BillingDocument)."
11475+
},
11476+
{
11477+
"name": "List Products",
11478+
"description": "List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11479+
},
11480+
{
11481+
"name": "Get Product",
11482+
"description": "Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product)."
11483+
},
11484+
{
11485+
"name": "Update Product",
11486+
"description": "Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET."
11487+
},
11488+
{
11489+
"name": "List Material Stock",
11490+
"description": "List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A_MatlStkInAcctMod). The entity uses an 11-field composite key (Material, Plant, StorageLocation, Batch, Supplier, Customer, WBSElementInternalID, SDDocument, SDDocumentItem, InventorySpecialStockType, InventoryStockType) — query with $filter on these fields instead of a direct key lookup."
11491+
},
11492+
{
11493+
"name": "List Material Documents",
11494+
"description": "List material document headers (goods movements) from SAP S/4HANA Cloud (API_MATERIAL_DOCUMENT_SRV, A_MaterialDocumentHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11495+
},
11496+
{
11497+
"name": "List Purchase Requisitions",
11498+
"description": "List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader) with optional OData $filter, $top, $skip, $orderby, $select, $expand. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled."
11499+
},
11500+
{
11501+
"name": "Get Purchase Requisition",
11502+
"description": "Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled."
11503+
},
11504+
{
11505+
"name": "Create Purchase Requisition",
11506+
"description": "Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, A_PurchaseRequisitionHeader). PurchaseRequisition is auto-assigned by SAP from the document number range; provide line items via the to_PurchaseReqnItem deep-insert array. Note: API_PURCHASEREQ_PROCESS_SRV is deprecated since S/4HANA Cloud Public Edition 2402; the successor is API_PURCHASEREQUISITION_2 (OData v4). This tool still works against tenants where the legacy service is enabled."
11507+
},
11508+
{
11509+
"name": "Update Purchase Requisition",
11510+
"description": "Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV; deprecated since S/4HANA 2402, successor is API_PURCHASEREQUISITION_2 OData v4). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11511+
},
11512+
{
11513+
"name": "List Purchase Orders",
11514+
"description": "List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11515+
},
11516+
{
11517+
"name": "Get Purchase Order",
11518+
"description": "Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder)."
11519+
},
11520+
{
11521+
"name": "Create Purchase Order",
11522+
"description": "Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_PurchaseOrder). PurchaseOrder is auto-assigned by SAP from the document number range; provide line items via the body parameter."
11523+
},
11524+
{
11525+
"name": "Update Purchase Order",
11526+
"description": "Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates."
11527+
},
11528+
{
11529+
"name": "List Supplier Invoices",
11530+
"description": "List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice) with optional OData $filter, $top, $skip, $orderby, $select, $expand."
11531+
},
11532+
{
11533+
"name": "Get Supplier Invoice",
11534+
"description": "Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYear) from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, A_SupplierInvoice)."
11535+
},
11536+
{
11537+
"name": "OData Query (advanced)",
11538+
"description": "Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping."
11539+
}
11540+
],
11541+
"operationCount": 37,
11542+
"triggers": [],
11543+
"triggerCount": 0,
11544+
"authType": "none",
11545+
"category": "tools",
11546+
"integrationTypes": ["other", "developer-tools"],
11547+
"tags": ["automation"]
11548+
},
1138211549
{
1138311550
"type": "search",
1138411551
"slug": "search",

0 commit comments

Comments
 (0)