Adding multiple entitlements with separate api calls in one request

Which IIQ version are you inquiring about?

IIQ 8.4

Share all details about your problem, including any error messages you may have received.

Hi All, I am working on Add Entitlement operation with a web service connector application. I am facing a logic issue where I need to make multiple api calls in one request of adding one or many entitlement to an identity through Manage User Access in SailPoint. The scenario is as below:

  1. Identity A to raise a request to add 3 entitlements:
    a. Entitlement A
    b. Entitlement B
    c. Entitlement C

Based on API specs the context url value in <<>> are supposed to be the name of the entitlement raised in the request.

Context URL

/iam/users/<<Entitlement A>>/members
/iam/users/<<Entitlement B>>/members
/iam/users/<<Entitlement C>>/members

How can I dynamically read the values <<Entitlement A>>, <<Entitlement B>> and <<Entitlement C>> from the adding entitlements request made?

Hi @shijingg ,

You can add this to the XML of the application.

<entry key="addRemoveEntInSingleReq">
<value>
<Boolean>true</Boolean>
</value>
</entry>

This will send through all the entitlements in a single request.

Thanks,

Hi @dylanfoggan, I am unable to use this solution as the api is built to accept the name of the entitlement in the context url and not in the body of the request

What you could perhaps do is then create a WebServiceBeforeOperationRule and execute each of the API calls in the Rule. Then in the operation just configure it to be a “Get Account”.

Let me know if this makes sense,

Thanks

Hi @dylanfoggan do you have an example of how I can call api in an WebServiceBeforeOperationRule and where should the entitlement value be read from?

Yep sure, just give me a moment and I will provide and explain an example.

Thanks,

1 Like

Hey @shijingg ,

One point to raise is that I have not tested this code but I think it should give you an idea on the solution to this.

First, we will need to add the XML entry:

<entry key="addRemoveEntInSingleReq">
<value>
<Boolean>true</Boolean>
</value>
</entry>

This is going to allow us to send all of the entitlements that the user requested in a List of items so that we can modify it in the rule.

Next we have the BeforeOperationRule:

<Source>
  import java.util.Map;
  import java.util.List;
  import java.util.HashMap;
  import java.util.ArrayList;
  import sailpoint.tools.Util;
  import connector.common.JsonUtil;  
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;

  List entitlements = new ArrayList();

  // Looping through the account requests and attribute requests to get the entitlement values

  for(AccountRequests accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
  {
    for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
    {
      entitlements = attributeRequest.get("entitlements"); // This is going to be an list of entitlements the user requests for *NB this attribute name is based on what you defined in the schema
    }
  }

  if(entitlements.isNotNullOrEmpty())
  {
    for(String entitlement: entitlements)  // looping through the entitlments to make the api calls
    {
      restClient.executeGet("iam/users/{{entitlement}}/members", requestEndPoint.getHeader(), requestEndPoint.getAllowedStatus()); // This will loop through all the entitlements and make the API calls
    }
  }
  
  return requestEndPoint;
</Source>

What this is going to do is Only execute the API calls for the request, it is not going to handle the responses for the requests ( If the API responses are needed we would need to make adjustments ).

Then in the operation:

We have a GET single-user request to kind of trick IIQ to execute the operation anyway. Then you can just return the response from the single-user request and the access request should complete.

Let me know if this makes sense.

Thanks,

1 Like

Hi @dylanfoggan

Thanks for the code. I am facing issues with getAllowedStatus() method when executing the end point. I am not able to find much information on this method as well. Would need your assistance on this.

Hi @shijingg ,

My apologies for this, it should be this instead:

requestEndPoint.getResponseCode()

Thanks,

Hi @dylanfoggan

any chance I can use
restClient.executePut(finalURL, requestEndPoint.getBody(), requestEndPoint.getHeader(), requestEndPoint.getResponseCode());

instead? Because I think get does not work for my use case however, I tried with Execute put and keep getting error 400

Hi @shijingg ,

What you have configured for the PUT request is correct.

What I would suggest you take a look at would be to ensure in the operation you have set the rule on has the response code set:

image

Next, if that still presents the same error of 400. I suggest you add all the values in that request you attempted to make in Postman and see if the error happens there.

We would need to ensure the body and URL are in the correct format.

Thanks,

Hi @dylanfoggan, let me share my current configurations. As I am still getting error 400. I tried to hard code all the values using the UI and managed to make the call successfully. Need your help to advise am I missing anything else?

FYI, I am using the before operation rule to dynamically get the access token as well.

<Source>import org.apache.http.client.HttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.HttpResponse;
  import org.apache.http.util.EntityUtils;
  import org.json.JSONObject;
  import java.util.ArrayList;
  import java.util.List;
  import sailpoint.tools.Util;
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import connector.common.JsonUtil;  
  import sailpoint.connector.webservices.EndPoint;

  String tokenUrl = "tokenUrl";
  String clientId = "clientId";
  String clientSecret = "clientSecret";
  String scope = "scope";
  String namespace = "namespace";

  HttpClient httpClient = HttpClients.createDefault();
  HttpPost httpPost = new HttpPost(tokenUrl);

  List params = new ArrayList();
  params.add(new BasicNameValuePair("grant_type", "client_credentials"));
  params.add(new BasicNameValuePair("client_id", clientId));
  params.add(new BasicNameValuePair("client_secret", clientSecret));
  params.add(new BasicNameValuePair("scope", scope));

  httpPost.setEntity(new UrlEncodedFormEntity(params));

  HttpResponse response = httpClient.execute(httpPost);

  int statusCode = response.getStatusLine().getStatusCode();
  if (statusCode == 200) {
    String responseBody = EntityUtils.toString(response.getEntity());
    JSONObject jsonResponse = new JSONObject(responseBody);
    String accessToken = jsonResponse.getString("access_token");
    String finalAccessToken = "";
    System.out.println("Access Token: " + accessToken);
    log.error("Access Token: " + accessToken);

    finalAccessToken = "Bearer " + accessToken;
    requestEndPoint.addHeader("x-api-key", "x-api-key");
    requestEndPoint.addHeader("Authorization", finalAccessToken );
    requestEndPoint.addHeader("Accept", "application/json");
    requestEndPoint.addHeader("Content-Type", "application/json");


    // Start of getting context url
    List entitlements = new ArrayList();
    String finalURL = "";
    // Looping through the account requests and attribute requests to get the entitlement values

    for(AccountRequest accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
    {
      for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
      {
        String attrName = attributeRequest.getName();

        if(attrName.equals("groupName")){
          entitlements.add(attributeRequest.getValue());
        }
      }
    }

    if(!(entitlements.isEmpty()))
    {
      for(String entitlement: entitlements)  // looping through the entitlments to make the api calls
      {
        
        finalURL = requestEndPoint.getFullUrl() + entitlement + "/members";

        try {
          restClient.executePut(finalURL, requestEndPoint.getBody(), requestEndPoint.getHeader(), requestEndPoint.getResponseCode());
        } catch (Exception e) {
          log.error("Error executing PUT request: " + e.getMessage(), e);
        }
      }
    }
  } else {
    System.out.println("Error: " + statusCode + " - " + response.getStatusLine().getReasonPhrase());
  }
  </Source>



Hi @dylanfoggan bumping this thread again. Still stuck with Error 400. Would need your help to see if you have any suggestions?

Hi @shijingg,

Some suggestions.

I recommend you avoid trying to authenticate before a single operation unless the API being required to execute is outside the scope. Instead, you should attempt to create the Custom Authentication operation and return the access_token from there so that it is saved to the connector.

This will allow you to reference the token set by the operation in any operation or any rule you use in the connector.


Next, let us look at the code you shared attempting to add the headers.

We can exchange the requestEndPoint.addHeader(“Authorization”, finalAccessToken ); and instead add it to the operation.

image


Going onto the 400 Bad Request you received.

I don’t see anything evident that is incorrect in how you set the API execution line but can you perhaps try logging all the values you are trying to set and confirm to me that they all look correct and are expected as is? ( Please log it where the red line is in the image ). It would look like this:

log.error("Final URL: " + finalURL);
log.error("Final URL: " + requestEndPoint.getBody());
log.error("Final URL: " + requestEndPoint.getHeader());
log.error("Final URL: " + requestEndPoint.getResponseCode());

Let me know if this helps you.

Thanks,

1 Like

Hi @dylanfoggan thank you for the advice on the custom authentication. That part works. However, I am still getting error 400. Let me share the latest code I have put in place.

I added code to set the context url as I am getting Error 404 as well. But somehow it still calls the context url based on what is set in the UI.

<Source>import org.apache.http.client.HttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.HttpResponse;
  import org.apache.http.util.EntityUtils;
  import org.json.JSONObject;
  import java.util.ArrayList;
  import java.util.List;
  import sailpoint.tools.Util;
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import connector.common.JsonUtil; 
  import connector.common.Util;
  import sailpoint.connector.webservices.EndPoint;


  // Start of getting context url
  List entitlements = new ArrayList();
  String finalUrl = "";
  String contextUrl = "";
  // Looping through the account requests and attribute requests to get the entitlement values

  for(AccountRequest accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
  {
    emailList.add(accountRequest.getNativeIdentity());
    for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
    {
      String attrName = attributeRequest.getName();

      if(attrName.equals("groupName")){
        entitlements.add(attributeRequest.getValue());
      }
    }
  }

  if(!(entitlements.isEmpty()))
  {
    for(String entitlement: entitlements)  // looping through the entitlments to make the api calls
    {
      contextUrl = requestEndPoint.getContextUrl() + entitlement + "/members";
			requestEndPoint.setContextUrl(contextUrl);
      finalURL = requestEndPoint.getFullUrl() + "/namespace/group/" + entitlement + "/members";
      try {
        log.error("Final URL: " + finalURL);
        log.error("Body: " + requestEndPoint.getBody());
        log.error("Header: " + requestEndPoint.getHeader());
        log.error("Reponse Code: " + requestEndPoint.getResponseCode());
        restClient.executePut(finalURL, requestEndPoint.getBody(), requestEndPoint.getHeader(), requestEndPoint.getResponseCode());

      } catch (Exception e) {
        log.error("Error executing PUT request: " + e.getMessage(), e);
      }
    }

  }

  </Source>

As for the logging, looks as is to me
finalURL

body

header

response code

Hi @shijingg ,

Since you have confirmed that the parameters are all correct in the request.

I have modified the code you shared a bit. Could you please perhaps give it a go and let me know.

<Source>
  import org.apache.http.client.HttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.HttpResponse;
  import org.apache.http.util.EntityUtils;
  import org.json.JSONObject;
  import java.util.ArrayList;
  import java.util.List;
  import sailpoint.tools.Util;
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import connector.common.JsonUtil; 
  import connector.common.Util;
  import sailpoint.connector.webservices.EndPoint;

  public String httpRequest(String requestUrl)
  {
    return application.getAttributeValue("genericWebServiceBaseUrl") + requestUrl; 
  }

  String finalUrl = "";
  String contextUrl = "";
  List entitlements = new ArrayList();

  for(AccountRequest accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
  {
    emailList.add(accountRequest.getNativeIdentity());

    for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
    {
      String attrName = attributeRequest.getName();

      if(attrName.equals("groupName"))
      {
        entitlements.add(attributeRequest.getValue());
      }
    }
  }

  if(!(entitlements.isEmpty()))
  {
    for(String entitlement : entitlements)  // looping through the entitlments to make the api calls
    {
      log.error("Final URL: " + httpRequest("/namespace/group/" + entitlement + "/members"));
      log.error("Body: " + requestEndPoint.getBody());
      log.error("Header: " + requestEndPoint.getHeader());
      log.error("Reponse Code: " + requestEndPoint.getResponseCode());

      String response = restClient.executePut(httpRequest("/namespace/group/" + entitlement + "/members"), requestEndPoint.getBody(), requestEndPoint.getHeader(), requestEndPoint.getResponseCode());

      log.error("Response: " + response);
    }
  }

  return requestEndPoint;
</Source>

If the operation still throws an error that is fine but lets just first confirm that the APIs are executing correctly.

Thanks,

Hi @dylanfoggan I am getting a different error now with the new code.The application script threw an exception: sailpoint.connector.ConnectorException: 401 but the 4 lines of logging looks the same.

import org.apache.http.client.HttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.HttpResponse;
  import org.apache.http.util.EntityUtils;
  import org.json.JSONObject;
  import java.util.ArrayList;
  import java.util.List;
  import sailpoint.tools.Util;
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import connector.common.JsonUtil; 
  import connector.common.Util;
  import sailpoint.connector.webservices.EndPoint;
  

  public String httpRequest(String requestUrl)
  {
    return application.getAttributeValue("genericWebServiceBaseUrl") + requestUrl; 
  }
  // Start of getting context url
  List entitlements = new ArrayList();
  String finalUrl = "";
  String contextUrl = "";
  // Looping through the account requests and attribute requests to get the entitlement values

  for(AccountRequest accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
  {
    //emailList.add(accountRequest.getNativeIdentity());
    for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
    {
      String attrName = attributeRequest.getName();

      if(attrName.equals("groupName")){
        entitlements.add(attributeRequest.getValue());
      }
    }
  }

  if(!(entitlements.isEmpty()))
  {
    for(String entitlement: entitlements)  // looping through the entitlments to make the api calls
    {
      log.error("Final URL: " + httpRequest("/iam/namespace/group/" + entitlement + "/members"));
      log.error("Body: " + requestEndPoint.getBody());
      log.error("Header: " + requestEndPoint.getHeader());
      log.error("Reponse Code: " + requestEndPoint.getResponseCode());

      String response = restClient.executePut(httpRequest("/iam/namespace/group/" + entitlement + "/members"),requestEndPoint.getBody(),requestEndPoint.getHeader(),requestEndPoint.getResponseCode());

      log.error("Response: " + response);
    }

  }
  return requestEndPoint;


Hi @dylanfoggan I think the problem was that restClient.executeput is not supported in my version of SailPoint.

Sharing the final code that worked.

import org.apache.http.client.HttpClient;
  import org.apache.http.impl.client.HttpClients;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.entity.UrlEncodedFormEntity;
  import org.apache.http.message.BasicNameValuePair;
  import org.apache.http.HttpResponse;
  import org.apache.http.util.EntityUtils;
  import org.json.JSONObject;
  import java.util.ArrayList;
  import java.util.List;
  import sailpoint.tools.Util;
  import sailpoint.object.Application;
  import sailpoint.object.ProvisioningPlan.AccountRequest;
  import sailpoint.object.ProvisioningPlan.AttributeRequest;
  import connector.common.JsonUtil; 
  import connector.common.Util;
  import sailpoint.connector.webservices.EndPoint;


  public String httpRequest(String requestUrl)
  {
    return application.getAttributeValue("genericWebServiceBaseUrl") + requestUrl; 
  }
  // Start of getting context url
  List entitlements = new ArrayList();
  String finalUrl = "";
  String contextUrl = "";
  // Looping through the account requests and attribute requests to get the entitlement values

  for(AccountRequest accountRequest : provisioningPlan.getAccountRequests()) // Get the Account Requests
  {
    //emailList.add(accountRequest.getNativeIdentity());
    for(AttributeRequest attributeRequest : accountRequest.getAttributeRequests()) // Get the Attribute Requests
    {
      String attrName = attributeRequest.getName();

      if(attrName.equals("groupName")){
        entitlements.add(attributeRequest.getValue());
      }
    }
  }

  if(!(entitlements.isEmpty()))
  {
    for(String entitlement: entitlements)  // looping through the entitlments to make the api calls
    {
      requestEndPoint.setFullUrl(httpRequest("/iam/namespace/group/" + entitlement + "/members"));
    }

  }
  return requestEndPoint;

Thank you for your help. :slight_smile:

Hi @dylanfoggan sorry I realised the configuration is wrong as it does not call the executeput each time so it will only run for the last entitlement selected. I need your help again. So Sorry.

Hi @shijingg ,

The 401 message you are receiving indicates that you are incorrectly authenticating to the API.

Some suggestions:

  • Confirm that the requestEndPoint.getHeader() you have logged is displaying a valid token that you could perhaps test in Postman.

Thanks,