Skip navigation
All Places > Products > RSA Identity Governance & Lifecycle > Blog > Authors Michael Bluteau

sap2csv:  Simple and Free java JCo3 utility to output any SAP Table into a csv

Check it out on:


  • Accept SAP JCo connection parameters and tablename
  • Outputs tablename.csv
  • Standard Output provides table description



See attached quick reference guide for SAP tables.

Michael Bluteau

AFX + CyberArk

Posted by Michael Bluteau Employee Sep 5, 2016

BIG DISCLAIMER:  The connectors provided on this page are only for educational purposes, and are not supported by RSA.


It's all about Privileged Accounts.  Identify the Privileged Accounts, make sure they are not exposed to the bad guys.


Where are they?  Some of them are hiding hardcoded in source code or configuration files.  That is the first target to zap in all organizations.


What is the next step?  Databases?  Are there any databases(or directories which are also databases) in the organization that contains one or more Privileged Accounts?  Well, how about that Identity Management/Provisioning product?  Isn't it the biggest nest of such Privileged Accounts in the corporate jungle?  Sounds like a big ball of risk, right?


What can be done about Privileged Accounts stored in the IdM/IAM product?  A first step would be at a minimum to rotate the passwords, so people who have left the organization cannot successfully guess the passwords one or 2 years from now.  How about we zap the passwords like we did for source code and config files?  What would be required?


CyberArk and other Privileged Account Management vendors have introduced the concept of password vaulting, initially to eliminate those passwords in source code and config files.  Basically, an application that needs to connect to another application using a Privileged Account will fetch the password in memory from Password Vault and use it to establish the connection, without ever committing the password to its database or configuration.  Each connection may be established using a different password.  The application will be authenticated using various factors(machine ID, OS login, certificate, etc) in order to be able to retrieve password at runtime.


Now if we apply this strategy to an Identity Management solution, here is how it looks:



Two approaches can be considered:

   1- Modify the Identity Management solution to provide a global framework for all connectors to be able to consume    either the local database or a common module that calls the Password Vault to obtain credentials;

   2- Modify each individual connector so they can either use the local database or fetch credentials from Password    Vault.


While the first approach may sound like the best approach, it would force existing Identity Management products to be re-designed, which may take some time for most vendors to deliver.


The 2nd approach sounds like a good tactical approach, and offers a quick solution with redesign limited to individual connectors.  Higher risk connectors could be targeted first.


Now comes AFX.  AFX provides a few things that actually makes it very easy to adapt to meet the bill:

   1- A somewhat easy to use SDK for building new connectors, Java Code Based connector;

   2- Capabilities, which are readily consumable by other components(Business Sources, Workflows).


So the idea is to enable each capability that we need in a new connector, using Java Code Based, unit test the capabilities, and then turn the connector to active and follow the typical configuration steps.


So I decided to take a shot at it, even if I am not a real developer.  Once we have enough examples for each type of capabilities and target applications(LDAP, Database, SOAP/REST, Java API, etc) copy and paste becomes the name of the game.  Building a demo environment with multiple connectors allow for articulating the Use Case and Value, while also providing a way to evaluate the effort involved, which is less than what I thought it would be originally, thanks to the Java Code Based connector and AFX Capabilities.


Of course, the connectors we can build with Java Code Based are not standard connectors, so from a supportability perspective, we need a different strategy, but they do look a lot like standard connectors once they work, and one can export/import them between environments like standard connectors.


The following is work in progress, but within a few days I was able to come up with a good list of working connectors, some of which already features most capabilities we would need in a real deployment, and others can be easily extended with Copy & Paste.


First, we need a working CyberArk vault and a Safe where we can manage AFX Passwords.


I will not get into the details for the CyberArk installation and configuration, but that is the first step.  You also need to install and configure Credential Provider and the client must be installed and configured on the AFX server.  See CyberArk document:  Credential Provider and ASCP Implementation Guide.pdf


Then we can take a quick look at the connectors I have working so far.


Active Directory connector, password is replaced by Safe and Object information.


N.B. I initially tried to use JNDI but ran into some limitations and switched to unboundid.


Capabilities already included are createAccount, deleteAccount, resetPassword, addAccountToGroup, removeAccountFromGroup, enableAccount, disableAccount, updateAccount and moveAccount.


Oracle connector is based on sample connector included with Java Code Based sample connector.


Capabilities right now are limited to one for testing but Oracle application specific SQL can be added to source code easily for each capability.  I will investigate other options to make it more generic.


Test capability in source code:



public JCBCStatus disableAccount(Map<String, String> settings, Map<String, Object> params) throws Exception {

String username = (String) params.get("UserName");

String query = String.format("INSERT INTO AVUSER.A_TEST_TABLE(USERNAME) values('Username = "+username+"')");

executeQuery(settings, query);

return JCBCStatus.success();



For the database connectors, you may run into an issue when you try to upload the jar files.  I had to copy them under the AFX app on the server and touch  mule-config.xml to restart the connector(without redeploying the files, which deletes the app folder).


For the SAP connector, like the Active Directory, it includes most needed capabilities.  I basically just vault enable my earlier SAP connector:


The ServiceNow connector is more an example of a Web Services(REST, SOAP) connector that can be used for both on-prem and cloud apps. For now, it is configured to create Request Items for manual provisioning.


ServiceNow capabilities for remote provisioning:  createAccount, deleteAccount, enableAccount, disableAccount.


The IMG connector is pointing back to L&G and illustrates how curl can be used to quickly build a Web Services based connector by scripting curl script in capability.  It is actually going through 3 steps:

   1- Retrieve password from vault using command line CyberArk SDK client;

   2- Use credentials to obtain a session token from L&G;

   3- Pass session token to REST call to add account to application role.


While this type of connector is less secure, potentially exposing the password in log files, it offers a quick a dirty approach, which could be considered secure if the password rotation frequency is high enough, etc.  I wanted to include it as an alternative to the java based httpclient example.


A few things to keep in mind:

   1- Java Code Based connector does not allow for uploading files that are not jar.  You may need to copy to app folder    and touch mule-config.xml to restart connector(without a re-deploy).  E.g. for SAP, you need to copy    everytime you redeploy the connector;

   2- A jar is a renamed zip.  If you modify the source and re-compile, you will need to upload new jar, then delete the     original one.  If the editor is showing a Java error after the delete, just click ok and ignore;

   3- jar files created with Java Code Based include source code (.java) and a script for compiling. I typically use    notepad to edit source code and I compile on L&G server so I have the right java version for the connectors;

   4- I will be updating this blog with more connectors, but I wanted to provide an example of each(LDAP, Web Services,    Database, JAVA API, command line) to get those who want to explore started.

First, I need to mention that this is NOT supported, since we need to edit installation scripts in order to deal with the situation when no DNS record exists for our server, as is the case for a test or demo environment.


For a supported installation script, please refer to official documentation.


this also works for previous versions of RSA L&G, e.g. Via L&G 7.0.

I posted SAP collectors and connectors earlier on RSA Link, and this time I am porting my recent AFX Connector for SAP with extra capabilities(AddAccountToGroup, AddProfile or AddCompositeProfile, Lock/Unlock) and attributes(e.g. email, department) to the JavaCodeBased AFX Connector, which is pretty much aimed at developers as a SDK.  However, good thing, once a connector is created with custom Java and jars using the toolkit(available here: ) the connector can be exported and imported to a new environment without having to manually recreate it.


My original connector calling similar Java code but using SSH Connector required the use of a properties file, and was also using output files for the SAP Destination provider.  I improve on those aspects by pushing the settings and credentials to where it should belong, the Settings tab for the connector, and I eliminated the output file Destination provider, and cleaned up the Java a bit.  The original connector can be found here:  SAP BAPI/Jco Connector using AFX(SSH + Java)


Let's get started first with a high level overview, then we can dive into the JavaCodeBased tutorial portion.  First you will need an instance of L&G that can reach the outside world or your test SAP, and then if you don't have a test SAP environment, you can get one from IDES (see  ).  Then you can import the connector, change the credentials and system info on the Settings page, and then you can start testing the capabilities.  Note that not all instances from IDES seem to provide the right permissions through Jco for the service account they provide, you may need to ask for the permissions.


After importing the connector, you need to provide your SAP information and credentials:



Then you can take a look at the capabilities, which you need to create manually if you start with a brand new JavaCodeBased connector:






Now there is a gap with the 7.0 version of JavaCodeBased connector, it only allows for importing jar files, while SAP requires also library which is not a jar.  The temporary workaround(until a fix or better workaround is identified) is to upload the file manually under the connector, and touch the config file to restart the connector without redeploying it(which would wipe out the file):



Now we can start testing capabilities.  Let's start with CreateAccount:




In order to test that CreateAccount worked, you can just try to login to SAP with new account/temporary password.  You will be prompted to select a new password:




Then you can test AddAccountToGroup.  For this, we will use SU01D:






Then add Profile(AddAppRoleToAccount):



Then add Role to Account:



Then Lock and Unlock Account.  We will use SE11 to show USR02 table to confirm that the status is changed on the SAP side:








Then lastly, ResetPassword, for which you can just try to login again with new temporary password:



That's already more capabilities and attributes than the official SAP connector.  It is possible to add support for other capabilities and attributes, and one way to figure out what ABAP functions are available and what is the type for each attribute is to 1- Google to find out the name of the BAPI function that does what you want 2- Use SE37 Function Builder to test those functions(see last section below).


Once you know which ABAP you want, you can edit the source code and recompile.  Since a jar is just a renamed zip file, you can expand the jar archive and rezip/rename after your changes.  When you update the connector, upload the new jar with a different name, then delete the old jar and click OK even if the screen does not come back(seems to be a bug in 7.0).  Confirm that your new jar is uploaded in config.


Since I am not a developer, I don't have Eclipse or fancy tools on my desktop, so I just use Notepad to edit the Java source file.  Here is a quick tutorial on the way the file works for the SAP capabilities, but you could use this example or the one provided with the toolkit(Oracle) to build a new connector.


First we will create the Java as a package, and then add the needed dependencies(extra ones here, we could clean that up):




import java.util.ArrayList;

import java.util.List;

import java.util.Map;







import java.util.Hashtable;

import java.util.Properties;




import com.aveksa.afx.common.connector.service.JavaCodeBasedConnectorBase;



import com.aveksa.afx.common.connector.service.response.JCBCStatus;














Then we need to create a class that extends JavaCodeBasedConnectorBase :

public class SapImpl extends JavaCodeBasedConnectorBase {


private static final String PARAM_JCO_USER = "JcoUser";

private static final String PARAM_JCO_PASSWD = "JcoPassword";

private static final String PARAM_JCO_CLIENT = "JcoClient";

private static final String PARAM_JCO_ASHOST = "JcoASHost";

private static final String PARAM_JCO_SYSNR = "JcoSysNr";

private static final String PARAM_JCO_LANG = "JcoLang";

private static final String PARAM_JCO_POOL_CAPACITY = "JcoPoolCapacity";

private static final String PARAM_JCO_PEAK_LIMIT = "JcoPeakLimit";

//private static final String DRIVER_CLASS_NAME = "";


public List<JCBCProperty> getSettings() {

final String category = "Authentication";

JCBCProperty JcoASHost  = createProperty(PARAM_JCO_ASHOST, PARAM_JCO_ASHOST, PARAM_JCO_ASHOST, category,JCBCPropertyType.STRING);

JCBCProperty userName = createProperty(PARAM_JCO_USER, PARAM_JCO_USER, PARAM_JCO_USER, category, JCBCPropertyType.STRING);

JCBCProperty password = createProperty(PARAM_JCO_PASSWD, PARAM_JCO_PASSWD, PARAM_JCO_PASSWD, category, JCBCPropertyType.PASSWORD);

JCBCProperty JcoClient  = createProperty(PARAM_JCO_CLIENT, PARAM_JCO_CLIENT, PARAM_JCO_CLIENT, category,JCBCPropertyType.STRING);

JCBCProperty JcoSysNr  = createProperty(PARAM_JCO_SYSNR, PARAM_JCO_SYSNR, PARAM_JCO_SYSNR, category,JCBCPropertyType.STRING);

JCBCProperty JcoLang  = createProperty(PARAM_JCO_LANG, PARAM_JCO_LANG, PARAM_JCO_LANG, category,JCBCPropertyType.STRING);


JCBCProperty JcoPeakLimit  = createProperty(PARAM_JCO_PEAK_LIMIT, PARAM_JCO_PEAK_LIMIT, PARAM_JCO_PEAK_LIMIT, category,JCBCPropertyType.STRING);

List<JCBCProperty> parameterList = new ArrayList<JCBCProperty>(8);









return parameterList;

We are basically capturing the settings from the Settings tab of the Connector, 8 values, and storing that in a reusable container.
We also need a private method that we call in that class:

private JCBCProperty createProperty(String name, String displayName, String description, String category, JCBCPropertyType type) {

JCBCProperty prop =  new JCBCProperty(name, displayName, description);






return prop;

Then we need to define our Destination Data Provider for SAP:

    static class MyDestinationDataProvider implements DestinationDataProvider


        private DestinationDataEventListener eL;

        private Properties ABAP_AS_properties;


        public Properties getDestinationProperties(String destinationName)


            if(destinationName.equals("ABAP_AS") && ABAP_AS_properties!=null)

                return ABAP_AS_properties;


            return null;

            //alternatively throw runtime exception

            //throw new RuntimeException("Destination " + destinationName + " is not available");


        public void setDestinationDataEventListener(DestinationDataEventListener eventListener)


            this.eL = eventListener;


        public boolean supportsEvents()


            return true;



        void changePropertiesForABAP_AS(Properties properties)




                ABAP_AS_properties = null;





                if(ABAP_AS_properties==null || !ABAP_AS_properties.equals(properties))


                    ABAP_AS_properties = properties;





Then we are down to the Capabilities.  First one is CreateAccount.  Here is the first portion, almost identical for all capabilities except the list of Parameters is mapped to Connector capabilities parameters:

public JCBCStatus createAccount(Map<String, String> settings, Map<String, Object> params) throws Exception {
  String username = (String) params.get("UserName");
  String password = (String) params.get("Password");
  String firstname = (String) params.get("firstname");
  String lastname = (String) params.get("lastname");
  String email = (String) params.get("email");
  String department = (String) params.get("department");
        Properties connectProperties = new Properties();
        connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, settings.get(PARAM_JCO_ASHOST));
        connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR,  settings.get(PARAM_JCO_SYSNR));
        connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, settings.get(PARAM_JCO_CLIENT));
        connectProperties.setProperty(DestinationDataProvider.JCO_USER,   settings.get(PARAM_JCO_USER));
        connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, settings.get(PARAM_JCO_PASSWD));
        connectProperties.setProperty(DestinationDataProvider.JCO_LANG,   settings.get(PARAM_JCO_LANG));
        connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, settings.get(PARAM_JCO_POOL_CAPACITY));
        connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT,    settings.get(PARAM_JCO_PEAK_LIMIT));
        MyDestinationDataProvider myProvider = new MyDestinationDataProvider();  
     JCoDestination destination = JCoDestinationManager.getDestination("ABAP_AS");






Note that we perform a test for every capability to check is DestinationDataProvider is already registered. Otherwise if you try a second capability without restarting the connector, you will get a Java error stating that the Destination Data Provider is already registered.


Another approach, similar to the one I used in my previous SSH connector, was to define the connectProperties in first main/first class, but the JavaCodeBased is complaining about a missing sapjco3 library, so it looks like it is performing some extra test when jar is uploaded, and this error was preventing me from uploading the jar. So a little extra code here, and the if condition to prevent the connector from trying to register the Destination multiple times.


And now the second portion, which is specific to each capability:





  JCoFunction bapiUserCreate = destination.getRepository().getFunctionTemplate("BAPI_USER_CREATE").getFunction();
              if(bapiUserCreate != null){
                JCoStructure myAddress = bapiUserCreate.getImportParameterList().getStructure("ADDRESS");
                  myAddress.setValue("FULLNAME",firstname+" "+lastname);
                   bapiUserCreate.getImportParameterList().setValue("USERNAME", username);
                JCoStructure myPassword = bapiUserCreate.getImportParameterList().getStructure("PASSWORD");
  return JCBCStatus.success();




Note that the ADDRESS is a structure that contains the attributes(FIRSTNAME, LASTNAME, etc) and that PASSWORD is also a structure with BAPIPWD as an attribute.  You can figure out the type of attribute and structure by investigating the BAPI_USER_CREATE function in SE37, best way is in Test mode.


The 2nd portion for DeleteAccount is simpler, and you will find similar examples with Lock and Unlock account:




JCoFunction bapiUserDelete = destination.getRepository().getFunctionTemplate("BAPI_USER_DELETE").getFunction();
              if(bapiUserDelete != null){
                   bapiUserDelete.getImportParameterList().setValue("USERNAME", username);
  return JCBCStatus.success();


Reset Password is a little bit different than setting the initial password, since we need to change the existing value:



JCoFunction bapiUserChange = destination.getRepository().getFunctionTemplate("BAPI_USER_CHANGE").getFunction();
              if(bapiUserChange != null){
                   bapiUserChange.getImportParameterList().setValue("USERNAME", username);
                JCoStructure myPasswordX = bapiUserChange.getImportParameterList().getStructure("PASSWORDX");
                JCoStructure myPassword = bapiUserChange.getImportParameterList().getStructure("PASSWORD");
  return JCBCStatus.success();


AddAccountToGroup is different since we are introducing tables:




JCoFunction bapiUserChange = destination.getRepository().getFunctionTemplate("BAPI_USER_CHANGE").getFunction();
              if(bapiUserChange != null){
                JCoStructure myGroupsX = bapiUserChange.getImportParameterList().getStructure("GROUPSX");
         JCoTable inputTable = bapiUserChange.getTableParameterList().getTable("GROUPS");
         inputTable.setValue("USERGROUP", group);
                   bapiUserChange.getImportParameterList().setValue("USERNAME", username);
  return JCBCStatus.success();


We also use tables for Profiles and Roles:



JCoFunction bapiUserChange = destination.getRepository().getFunctionTemplate("BAPI_USER_ACTGROUPS_ASSIGN").getFunction();
              if(bapiUserChange != null){
         JCoTable inputTable = bapiUserChange.getTableParameterList().getTable("ACTIVITYGROUPS");
         inputTable.setValue("AGR_NAME", approle);
                   bapiUserChange.getImportParameterList().setValue("USERNAME", username);
  return JCBCStatus.success();


Usually I just copy my source file to the L&G server and that is where I compile, using the simple script provided in jar archive.  I point to /home/oracle/mysap to find the sapjco3.jar and at compilation time.


Now if you want to explore and add new capabilities and attributes, here is a quick walkthrough for SE37:

See: List of BAPI's - ABAP Development - SCN Wiki






COMPANYX is associated to COMPANY etc, so for a Change you need to deal with both as shown in code above.


For Structure, you need to deal with both the attribute in address and its associated X value in ADDRESSX as illustrated in code above.


Example for a table.

This is an alternative SAP connector I have created for demonstration purpose.


See also this newer example that is based on JavaCodeBased AFX Connector:  New SAP AFX Connector - JavaCodeBased Tutorial


Unzip the archive under /home/oracle  to get 2 directories:  mysap contains libraries and the compiled classes, mysapconn contains the source code and compiling script, and also the properties file for connectivity to the SAP environment.


N.B.  You need to copy your sapjco3.jar and librairies to /home/oracle/mysap


You may need to copy .properties files to /root or homedir of user account in AFX connector(also attached).


You can modify the source code and add additional BAPI calls that you can invoke using AFX connector capabilities.  It's Java but with all the working examples and with SE37 to investigate capabilities like BAPI_USER_CREATE etc, you can figure out if you need to send an attribute as a structure, table element, etc.


Capabilities include:






-AddAccountToApplicationRole (it basically changes the AppRole)




Ever been looking for the (or a) XML collector?  You have a file in XML, let's say with some accounts in it, a simple XML file.  Is there a quick way to collect it without having to convert to CSV before?  Here is a quick example, based on a simple XML file.  I will provide a more complex example later.


XML file (you can download stuff.xml):


        <room num="1">
            <name>Room 1</name>
        <room num="2">
            <name>Room 2</name>
        <room num="3">
            <name>Room 3</name>
        <room num="4">
            <name>Room 4</name>
        <account id="accountA">
            <shortDesc>Acct A</shortDesc>
            <longDesc>Account A</longDesc>
        <account id="accountB">
            <shortDesc>Acct B</shortDesc>
            <longDesc>Account B</longDesc>
        <account id="accountC">
            <shortDesc>Acct C</shortDesc>
            <longDesc>Account C</longDesc>
        <account id="accountD">
            <shortDesc>Acct D</shortDesc>
            <longDesc>Account D</longDesc>



You need to create a Directory for where your file is located and Grant access to AVUSER using a SQL tool:


CREATE OR REPLACE DIRECTORY test_dir AS '/home/oracle/dbtest';
grant read on directory test_dir to AVUSER;


If you run the following Query in SQL Developer or another tool:

   SELECT  x.*
     FROM (SELECT xmltype(bfilename('TEST_DIR','stuff.xml'), nls_charset_id('WE8ISO8859P1')) xmlcol FROM dual) t,
          XMLTABLE ('/stuff/accounts/account'
                    PASSING t.xmlcol
                    COLUMNS AcctID VARCHAR2(15) PATH '@id',
                            shortDesc VARCHAR2(256) PATH 'shortDesc',
                            longDesc VARCHAR2(512) PATH 'longDesc',
                            alevel NUMBER(2) PATH 'level') x;


You get:

ACCTID  shortDesc  longDesc Alevel
accountAAcct Aaccount A1
accountBAcct Baccount B1
accountCAcct Caccount C1
accountDAcct Daccount D1


Now you need a collector for your file.  You can create a simple Account Collector using database:




Note that you need to use a subquery otherwise when you try to click Finish it will complain that columns are not in Query.


For more information and examples you can look at:

Ritesh Kesharwani: Load/Import XML file into database table (Oracle or SQL Server)