Wednesday, 19 December 2012

OIM 11.1.2.0.0 (11gR2) API - UserManager


In this post, I will explain UserManager service API provided by Oracle Identity Manager 11.1.2.0.0 (11gR2). OIM API can be used to develop clients which can communicate with OIM to perform various operations on OIM objects.

OIM supports two ways by which clients can be developed. They are :
1.    oracle.iam.platform.OIMClient
2.    Thor.API.tcUtilityFactory

Thor.API.tcUtilityFactory API was supported in the previous versions of OIM and it is still provided in the latest releases. But oracle.iam.platform.OIMClient is recommneded by Oracle and it should be preferred over tcUtilityFactory as it provides more robust way to build clients.
Here I will give you a few code snippets which are used to connect to OIM server and perform operations on OIM objects.

  1. Setup
    • Copy <IDM_HOME>/server/client/oimclient.zip on local machine. Extract the zip file. The extracted folder contains conf, lib and oimclient.jar.
    • Add oimclient.jar and libraries from lib folder to the classpath of the client project. The project should have following jars in the classpath :
      • commons-logging.jar
      • spring.jar
      • oimclient.jar
      • jrf-api.jar
      • wlfullclient.jar
                              Generation of wlfullclient.jar is explained at this location : http://docs.oracle.com/cd/E12840_01/wls/docs103/client/jarbuilder.html
    • Make sure JDK 1.6 and ANT 1.7 are present in the classpath

  1. Initialize
    • Create OIMClient instance by preparing the environment:

    •         Hashtable<Object, Object> env = new Hashtable<Object, Object>();
              env.put(OIMClient.JAVA_NAMING_FACTORY_INITIAL, "weblogic.jndi.WLInitialContextFactory");
              env.put(OIMClient.JAVA_NAMING_PROVIDER_URL, "t3://localhost:14000");
              System.setProperty("java.security.auth.login.config", "/home/ganesh/com.gsk.oim/config/authwl.conf");
              System.setProperty("OIM.AppServerType", "wls");
              System.setProperty("APPSERVER_TYPE", "wls");
              OIMClient oimClient = new OIMClient(env);
       
  2. Login
    • Once OIMClient is instantiated, the instance is used to login by providing correct username and password. login() method throws LoginException if login is unsuccessful:

    •         oimClient.login("xelsysadm", "Welcome1".toCharArray(), env);
             
       
  3. Lookup UserManager Service
    • UserManager is the Service class which is mainly used to perform various User operations. It can be instantiated in the following manner:

    •         UserManager userManager = oimClient.getService(UserManager.class);

On creation of userManager instance, we have an object which can connect and perform changes in OIM server. I will discuss a few basic scenarios which are widely used by OIM clients:

  • User Creation 
As I said earlier, UserManager is the instance which is used to perform ,as the name suggests, User specific actions. To create a User, we must pass a oracle.iam.identity.usermgmt.vo.User object to UserManager's create method. It takes UserId and a HashMap of user attributes as input. Following code snippet shows User Creation:
   
      HashMap<String, Object> userAttributeValueMap = new HashMap<String, Object>();
        userAttributeValueMap.put("act_key", new Long(1));
        userAttributeValueMap.put("User Login", userId);
        userAttributeValueMap.put("First Name", "Ganesh");
        userAttributeValueMap.put("Last Name", "Kamble");
        userAttributeValueMap.put("Email", "ganesh.kamble@abc.com");
        userAttributeValueMap.put("usr_password", "P1ssword");
        userAttributeValueMap.put("Role", "OTHER");
        User user = new User("Ganesh", userAttributeValueMap);
        userManager.create(user);

   
    Here we have created a userAttributeValueMap which stores mandatory attributes required for User Creation through OIM API. User object is created using an Unique Id and the HashMap. We pass this User object to UserManager.create(user) method. It returns UserManagerResult. We can verify the status by using UserManagerResult.getStatus() method. 

  • User Retrieval
    To retrieve the details of a user, UserManager provides several methods named getDetails() distinguished by the input parameters. I will take a method which takes input as userid, a set of attributes which are to be returned for each user and a boolean flag specifying whether the userid is the user login. If blank Set is given in the input, the method returns all the attributes of the User.

        Set<String> resAttrs = new HashSet<String>();
        User user = userManager.getDetails("Ganesh", resAttrs, true);


  • User Update
     UserManager provides methods named modify to update User details in OIM. I will talk about the method which takes User Object as input. In the following code snippet, I have retrieved a User with the loginId "Ganesh" and updated it with the changed user attributes
   
       Set<String> resAttrs = new HashSet<String>();
        User retrievedUser = userManager.getDetails("Ganesh", resAttrs, true);

        HashMap<String, Object> userAttributeValueMap = new HashMap<String, Object>();
        userAttributeValueMap.put("act_key", new Long(1));
        userAttributeValueMap.put("User Login", userId);
        userAttributeValueMap.put("First Name", "Ganesh");
        userAttributeValueMap.put("Last Name", "Kamble");
        userAttributeValueMap.put("Email", "ganesh.kamble@xyz.com");
        userAttributeValueMap.put("usr_password", "P@ssword");
        userAttributeValueMap.put("Role", "Other");

        User user = new User((String) retrievedUser.getAttribute("User Login"), userAttributeValueMap);
        userManager.modify(user);


  • User Search
OIM provides the support to search Users based on a particular criteria. We build a SearchCriteria based on which the Users need to be inquired, pass the SearchCriteria instance to UserManager.search() method as shown below:
   
       SearchCriteria searchCriteria = new SearchCriteria("Email", "ganesh.kamble@xyz.com", SearchCriteria.Operator.EQUAL);
        Set<String> attrNames = null;
        HashMap<String, Object> mapParams = new HashMap<String, Object>();
        mapParams.put("STARTROW", 0);
        mapParams.put("ENDROW", 1);
        List<User> users = null;
        users = userManager.search(searchCriteria, attrNames, mapParams);

   
     Here I have prepared a SearchCriteria which indicates the Email of the User should be EQUAL to ganesh.kamble@xyz.com. mapParams are the ConfigurationParameters which provides the functionality of more granular search. They can be STARTROW, ENDROW, SORTEDBY and SORTORDER. Here I have used STARTROW and ENDROW which indicates which subset of the complete result should be fetched. SORTEDBY sorts the result by User Login by default. It can be changed to the desired attribute. SORTEDORDER can be SortOrder.DESCENDING or SortOrder.ASCENDING latter being the default.

  • Lock/Unlock User Account
The following methods locks/unlocks a particular user account.
                 userManager.lock(userId, true, true);

                    Here first parameter is the id of the user to be locked. Second parameter indicates whether its a manual lock (true) or system lock (false). And third parameter indicates whether the userId is the UserLogin Id.

                userManager.unlock(userId, true);

                    Here first parameter is the id of the user to be locked. Second parameter is true if the userId is the UserLogin id.



    In this post, I have tried to cover some basic user operations on User entity. Each of the above operation comes in different flavours. You can find the entire set of OIM API at location : http://docs.oracle.com/cd/E27559_01/apirefs.1112/e28159/toc.htm . The detailed information on how to use the API is explained at this location : http://docs.oracle.com/cd/E27559_01/dev.1112/e27150/apis.htm#BCFGCGHI .

    The set of attributes which OIM understands is given below. These attributes can be passed in the HashMap as an input. Also when you inquire the user, these attributes will be returned.

    [ FA Territory, Employee Number, Middle Name, Manually Locked, usr_disabled, Display Name, LDAP Organization, usr_locked, Currency, Time Format, usr_created, usr_deprovisioning_date, Full Name, Country, Accessibility Mode, usr_pwd_expire_date, usr_pwd_cant_change, Email, usr_data_level, Automatically Delete On, Locked On, usr_login_attempts_ctr, Last Name, First Name, Locality Name, usr_policy_update, Street, Embedded Help, Department Number, usr_createby, usr_pwd_warned, Manager Login, Telephone Number, Manager First Name, usr_updateby, Home Phone, LDAP Organization Unit, usr_pwd_min_age_date, User Login, Title, Role, FA Language, Password Generated, usr_provisioning_date, usr_pwd_warn_date, Organization Name, usr_locale, usr_update, Date Format, usr_timezone, Mobile, usr_pwd_reset_attempts_ctr, End Date, Pager, usr_deprovisioned_date, Color Contrast, PO Box, usr_create, LDAP GUID, Xellerate Type, usr_change_pwd_at_next_logon, usr_provisioned_date, Common Name, Start Date, usr_manager_key, Number Format, usr_pwd_expired, Hire Date, User Name Preferred Language, Home Postal Address, Font Size, Manager Last Name, Description, Fax, Postal Code, act_key, usr_key, Common Name Generated, Status, Generation Qualifier, Postal Address, State, Manager Display Name, usr_pwd_never_expires, Initials, usr_pwd_must_change, LDAP DN ]

Thursday, 18 October 2012

How to invoke secured JAX-WS web service from a standalone client


     
       In this post I will explain the procedure of invoking secured JAX-WS web service from a standalone java client. It explains the problems you may face during the process.
There are several ways by which you can invoke a secured web service. I will explain it in two ways here. In this example I have a sample web service which is protected by the policy : wss_saml_or_username_token_service_policy.
I will not get the desired response if I try to invoke the protected web service without providing a proper username token in the soap header.
In weblogic, you can attach policies to web services with the help of owsm. Following block shows the snippet to be present in the soap header in order to get the client asserted by the web service provider which is protected by wss_saml_or_username_token_service_policy policy :

<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" S:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-Kr9QjWbqpQgxYI4CDWNxCg22">
<wsse:Username>administrator</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">Passw0rd</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>

The username in this case is 'administrator' and password is 'Passw0rd'

Here are the two ways in which you can add the above block in the header of the soap request on the client side :
1. First way is to add credentials in the RequestContext of the client port :

      List<CredentialProvider> credProviders =
          new ArrayList<CredentialProvider>();
      String username = "administrator";
      String password = "Passw0rd";
      CredentialProvider cp =
          new ClientUNTCredentialProvider(username.getBytes(),
                                          password.getBytes());
      credProviders.add(cp);
      Map<String, Object> requestContext =
          ((BindingProvider)sampleWebServicePort).getRequestContext();
      requestContext.put(BindingProvider.USERNAME_PROPERTY,username);
      requestContext.put(BindingProvider.PASSWORD_PROPERTY,password);
      sampleWebServicePort.callService();
     

2.  Second way is add to create the soap security header object which is to be added in the HandlerChain :

        try {
            CustomSOAPHandler sh = new CustomSOAPHandler();
            List<Handler> new_handlerChain = new ArrayList<Handler>();
            new_handlerChain.add(sh);
            ((BindingProvider)sampleWebServicePort).getBinding().setHandlerChain(new_handlerChain);
sampleWebServicePort.callService();
        } catch (Throwable e) {
            e.printStackTrace();
        }

Create a custom SOAPHandler class which will add the header in the soap request.
CustomSOAPHandler:


public class CustomSOAPHandler implements SOAPHandler<SOAPMessageContext> {

    private static final String AUTH_PREFIX = "wsse";
    private static final String AUTH_NS =
        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
.
.
.


    public boolean handleMessage(SOAPMessageContext context) {

        try {
            SOAPEnvelope envelope =
                context.getMessage().getSOAPPart().getEnvelope();
            SOAPFactory soapFactory = SOAPFactory.newInstance();
            SOAPElement wsSecHeaderElm =
                soapFactory.createElement("Security", AUTH_PREFIX, AUTH_NS);
            Name wsSecHdrMustUnderstandAttr =
                soapFactory.createName("mustUnderstand", "S",
                                       "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            wsSecHeaderElm.addAttribute(wsSecHdrMustUnderstandAttr, "1");
            SOAPElement userNameTokenElm =
                soapFactory.createElement("UsernameToken", AUTH_PREFIX,
                                          AUTH_NS);
            Name userNameTokenIdName =
                soapFactory.createName("id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
            userNameTokenElm.addAttribute(userNameTokenIdName,
                                          "UsernameToken-ORbTEPzNsEMDfzrI9sscVA22");
            SOAPElement userNameElm =
                soapFactory.createElement("Username", AUTH_PREFIX, AUTH_NS);
            userNameElm.addTextNode("administrator");
            SOAPElement passwdElm =
                soapFactory.createElement("Password", AUTH_PREFIX, AUTH_NS);
            Name passwdTypeAttr = soapFactory.createName("Type");
            passwdElm.addAttribute(passwdTypeAttr,
                                   "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
            passwdElm.addTextNode("Passw0rd");
            userNameTokenElm.addChildElement(userNameElm);
            userNameTokenElm.addChildElement(passwdElm);
            wsSecHeaderElm.addChildElement(userNameTokenElm);
            if (envelope.getHeader() == null) {
                SOAPHeader sh = envelope.addHeader();
                sh.addChildElement(wsSecHeaderElm);
            } else {
                SOAPHeader sh = envelope.getHeader();
                sh.addChildElement(wsSecHeaderElm);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return true;
    }

In this method, we are creating the Security element in the header of the soap request and on the server side it gets asserted successfully.
If the credentials are proper, then your service will get executed else it will throw an exception saying that the username token cannot be validated.


The second way does not require any extra jars to be present in the classpath whereas in first way you will need to add some weblogic jars in classpath in order to get it working.
The problem with the second way is if you try to test it with the help of jdeveloper, you will get the following error :

Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Unable to add security token for identity, token uri =http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID
at com.sun.xml.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:197)
at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:122)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:125)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:95)

If you analyze the stack trace carefully, you will notice that in this case jdeveloper uses classes from jars (glassfish jars) which are not part of jdk. And that's why you get the strange exception. If you run the same code from eclipse or through command line with just jdk, it will work.