I noticed that the NELM Bulk Update API uses a content-type of multipart/form-data in the body. Has anyone developed a script to automate the upload of NELM data? Our client will be using a SharePoint front-end to input NELM data, export that data to a CSV, and would like to then import the data using a scheduled job but I am not seeing an easy way to accomplish that with a multipart/form-data content type. Any guidance here would be helpful!
Hey @justinrhaines,
Thanks for posting! Let us do some poking around and get back to you shortly.
Hello @justinrhaines,
Is this the endpoint you are referring to?
https://developer.sailpoint.com/idn/api/beta/non-employee-records-bulk-upload
Do you know what programming language you will use for the script?
Here are some examples using cURL and Python:
cURL
curl --location --request POST 'https://{tenant}.api.identitynow.com/beta/non-employee-sources/2c9180887933afa701793d96d9cb5e6d/non-employee-bulk-upload' \
--header 'Authorization: Bearer {access_token}' \
--form 'data=@"/Users/my_user/Documents/non_employees_for_source_Contractors.csv"'
Python using requests library
import requests
url = "https://{tenant}.api.identitynow.com/beta/non-employee-sources/2c9180887933afa701793d96d9cb5e6d/non-employee-bulk-upload"
payload={}
files=[
('data',('non_employees_for_source_Contractors.csv',open('/Users/my_user/Documents/non_employees_for_source_Contractors.csv','rb'),'text/csv'))
]
headers = {
'Authorization': 'Bearer {access_token}'
}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
To get the source ID, you can use the following endpoint:
curl --location --request GET 'https://{tenant}.api.identitynow.com/beta/non-employee-sources' \
--header 'Authorization: Bearer {access_token}'
Look for the sourceId
of the source you want to update in the response:
[
{
"id": "ac110004-793d-14e4-8179-3d96e5300000",
"sourceId": "2c9180887933afa701793d96d9cb5e6d",
"name": "Contractors",
"description": "Non-employee contractors",
"approvers": [],
"accountManagers": [],
"created": "2021-05-05T17:32:49.989Z",
"modified": "2021-05-05T17:32:49.989Z",
"nonEmployeeCount": null
}
]
Yes, that is the API I am referring to. I would prefer to use Powershell as that is the clientâs standard and can be run using the Windows task scheduler, but I am open to other options as well.
Here is a PowerShell example using RestMethod
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer {access_token}")
$multipartContent = [System.Net.Http.MultipartFormDataContent]::new()
$multipartFile = '/Users/my_user/Documents/non_employees_for_source_Contractors.csv'
$FileStream = [System.IO.FileStream]::new($multipartFile, [System.IO.FileMode]::Open)
$fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
$fileHeader.Name = "data"
$fileHeader.FileName = "non_employees_for_source_Contractors.csv"
$fileContent = [System.Net.Http.StreamContent]::new($FileStream)
$fileContent.Headers.ContentDisposition = $fileHeader
$multipartContent.Add($fileContent)
$body = $multipartContent
$response = Invoke-RestMethod 'https://{tenant}.api.identitynow.com/beta/non-employee-sources/2c9180887933afa701793d96d9cb5e6d/non-employee-bulk-upload' -ContentType 'multipart/form-data' -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
Thanks, this is a great start but I am receiving the following response:
Invoke-RestMethod : {âmessagesâ:[{âlocaleOriginâ:âDEFAULTâ,âlocaleâ:âen-USâ,âtextâ:âContent type âapplication/x-www-form-urlencoded;charset=UTF-8â not supportedâ}],âdetailCodeâ:"400.0 Bad request
syntax",âtrackingIdâ:âf7de0c8cc9da420e97a8feec9b341cd7â}
At line:27 char:13
-
$response = Invoke-RestMethod 'https://amfidelity-sb.api.identitynow. âŚ
-
-
CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
-
FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Shouldnât this be a different content-type than x-www-form-urlencoded?
This should have a content-type of multipart/form-data. I havenât tested this myself as Iâm using OS X, but maybe you can try explicitly setting the content-type header by adding the following line in your headers section:
$headers.Add("Content-Type", "multipart/form-data")
If that doesnât work, then I found this Stack Overflow article that suggests adding an additional flag to the Invoke-RestMethod command:
$response = Invoke-RestMethod 'https://{tenant}.api.identitynow.com/beta/non-employee-sources/2c9180887933afa701793d96d9cb5e6d/non-employee-bulk-upload' -ContentType 'multipart/form-data' -Method 'POST' -Headers $headers -Body $body
Hi Collinâ
Yes I tried both of those methods and received a 500 Internal Server Error on both. Any other ideas?
Let me install a Windows VM and play around with this. Iâll see if I can figure out whatâs wrong and get back to you.
Hi Colinâ
Did you have a chance to test this with your Windows VM?
With the IdentityNow PowerShell Module we use multipart/form-data for the Join-IdentityNowAccount cmdlet.
We needed to convert the payload to UTF8 and add formatting to the CSV to get it to play nicely.
Here is an example where the payload is in CSV stringdata in the $csv variable.
$result = Invoke-restmethod -Uri "https://$($IdentityNowConfiguration.orgName).api.identitynow.com/cc/api/source/loadUncorrelatedAccounts/$source" `
-Method "POST" `
-Headers @{Authorization = "$($v3Token.token_type) $($v3Token.access_token)"; "Accept-Encoding" = "gzip, deflate, br"} `
-ContentType "multipart/form-data; boundary=----WebKitFormBoundaryU1hSZTy7cff3WW27" `
-Body ([System.Text.Encoding]::UTF8.GetBytes("------WebKitFormBoundaryU1hSZTy7cff3WW27$([char]13)$([char]10)Content-Disposition: form-data; name=`"file`"; filename=`"temp.csv`"$([char]13)$([char]10)Content-Type: application/vnd.ms-excel$([char]13)$([char]10)$([char]13)$([char]10)$($csv | out-string)$([char]13)$([char]10)------WebKitFormBoundaryU1hSZTy7cff3WW27--$([char]13)$([char]10)")) `
-UseBasicParsing
Cheers,
DR
Thank you @darrenjrobinson. Based on your code I was able to put together the following PowerShell code that will work.
$uri = 'https://{tenant}.api.identitynow.com/beta/non-employee-sources/{sourceId}/non-employee-bulk-upload'
$accessToken = '{accessToken}'
$inFile = 'C:\users\my_user\Documents\contractors.csv'
$fileName = 'contractors.csv'
$csvBytes = [System.IO.File]::ReadAllBytes($inFile)
$csvText = [System.Text.Encoding]::ASCII.GetString($csvBytes)
$result = Invoke-restmethod -Uri $uri `
-Method "POST" `
-Headers @{Authorization = "Bearer $accessToken"} `
-ContentType "multipart/form-data; boundary=----WebKitFormBoundaryU1hSZTy7cff3WW27" `
-Body ([System.Text.Encoding]::UTF8.GetBytes("------WebKitFormBoundaryU1hSZTy7cff3WW27$([char]13)$([char]10)Content-Disposition: form-data; name=`"data`"; filename=`"$fileName`"$([char]13)$([char]10)Content-Type: text/csv$([char]13)$([char]10)$([char]13)$([char]10)$($csvText)$([char]13)$([char]10)------WebKitFormBoundaryU1hSZTy7cff3WW27--$([char]13)$([char]10)")) `
-UseBasicParsing
$result
Brilliant. Great to see you got it working
Excellent, this is working as expected. Thanks everyone for your input!