Thursday, August 4, 2011

Couple of things you need to know about the User/Role API

The idea of the User/Role API is to abstract developers from the identity store where users and groups are kept. A developer can basically interact with any identity provider supported by Weblogic server using the same methods. The javadoc can be found here: http://download.oracle.com/docs/cd/E15523_01/apirefs.1111/e14658/toc.htm

In this post I want to alert you about two caveats:

1) User/Role API is able to query data from only one provider. If you want to query multiple identity stores, you need to go through an OVD Authenticator (or libOvd). And depending on how you get a handle to the identity store, the order in which providers are defined in Weblogic server Console as well as their CONTROL FLAGs do matter.

Shamelessly borrowing content from FMW Application Security Guide:

"OPSS initializes the identity store service with the LDAP authenticator chosen from the list of configured LDAP authenticators according to the following algorithm:

  1. Consider the subset of LDAP authenticators configured. Note that, since the context is assumed to contain at least one LDAP authenticator, this subset is not empty.
  2. Within that subset, consider those that have set the maximum flag. The flag ordering used to compute this subset is the following:
    REQUIRED > REQUISITE > SUFFICIENT > OPTIONAL
    
    Again, this subset (of LDAPs realizing the maximum flag) is not empty.
  3. Within that subset, consider the first configured in the context.


    The LDAP authenticator singled out in step 3 is the one chosen to initialize the identity store service."

Lack of such understanding is a big source of headache.

Weblogic server ships with DefaultAuthenticator as the out-of-box authentication provider with the CONTROL FLAG set as REQUIRED.  Customers typically want to retrieve users from an enterprise-wide LDAP server, like OID or Active Directory. They go ahead and define a new authenticator and put it as the first in the providers list. But they leave DefaultAuthenticator untouched, because they still want to leverage the weblogic user as the administrator. And when some application relying on the User/Role API is executed (Oracle's BPM and BIP are examples), a problem is just about to happen, because none of the users and groups defined in the enterprise-wide identity store are found. And the solution to this is pretty simple: switch DefaultAuthenticator's CONTROL FLAG from REQUIRED to SUFFICIENT. What happens now during authentication time is that if the user is not found in the first authenticator, the lookup falls back to DefaultAuthenticator, so leveraging weblogic user is not a problem. And that will also make the User Role API querying the identity provider that you want (the first in the list).

2) Depending on how you get a handle to the identity store, provider-specific metadata (user, password, address, root search base) won't be reused and you'll be forced to define it in code again (of course you can externalize them to some properties file, but it is still a double maintenance duty).

That said, let's examine possible ways of getting a handle to the identity store.

IdentityStoreFactoryBuilder builder = new IdentityStoreFactoryBuilder();
IdentityStoreFactory oidFactory = null;
Hashtable factEnv = new Hashtable();
// Creating the factory instance
factEnv.put(OIDIdentityStoreFactory.ST_SECURITY_PRINCIPAL, “cn=orcladmin”);
factEnv.put(OIDIdentityStoreFactory.ST_SECURITY_CREDENTIALS,“welcome1”);
factEnv.put(OIDIdentityStoreFactory.ST_LDAP_URL,“ldap://ldap.us.oracle.com:3060/”);
oidFactory = builder.getIdentityStoreFactory(“oracle.security.idm.providers.oid.
OIDIdentityStoreFactory”, factEnv);
Hashtable storeEnv = new Hashtable();
storeEnv.put(OIDIdentityStoreFactory.RT_SUBSCRIBER_NAME,"cn=users,dc=us,dc=oracle,dc=com");
IdentityStore oidStore = oidFactory.getIdentityStoreInstance(storeEnv);
// Use oidStore to perform various operations against the provider

Look at how specific this snippet is to OID and how we're passing metadata that is already available in the provider definition itself. By doing this, you do not incur in the problem described in my bullet #1, because you're going directly against a specific identity store. You're not leveraging the definitions in Weblogic server at all.

But if you do this...

JpsContextFactory ctxFactory = JpsContextFactory.getContextFactory();
JpsContext ctx = ctxFactory.getContext();
LdapIdentityStore idstoreService = (LdapIdentityStore)ctx.getServiceInstance(IdentityStoreService.class)
IdentityStore idStore = idstoreService.getIdmStore();

// Use idStore to perform various operations against the provider

you're delegating the provider lookup process to OPSS (Oracle Platform Security Services), and it will abide by those rules outlined in my bullet #1. Here, you don't have to redefine your connection metadata. You are simply reusing whatever is defined in Weblogic server and are not incurring in the problem mentioned in bullet #2. For consistency and manageability, this is a much better approach.

For the curious, the following is the necessary configuration in jps-config.xml to make this happen (see text in bold red). It is out-of-box available in any FMW install, so don't worry about it.

<serviceInstances>
...

<serviceInstance name="idstore.ldap" provider="idstore.ldap.provider">
     <property name="idstore.config.provider" value="oracle.security.jps.wls.internal.idstore.WlsLdapIdStoreConfigProvider"/>
     <property name="CONNECTION_POOL_CLASS" value="oracle.security.idm.providers.stdldap.JNDIPool"/>
</serviceInstance>
...
</serviceInstances>
...
<jpsContexts default="default">
        <jpsContext name="default">
            <serviceInstanceRef ref="credstore"/>
            <serviceInstanceRef ref="keystore"/> 
            <serviceInstanceRef ref="policystore.xml"/>
            <serviceInstanceRef ref="audit"/>
            <serviceInstanceRef ref="idstore.ldap"/>
            <serviceInstanceRef ref="trust"/>            
   <serviceInstanceRef ref="pdp.service"/>
        </jpsContext>

        ...
</jpsContexts>

7 comments:

  1. Hi Andre,

    Do you know where to find the javadocs for other providers other than OID?

    Can't find it!

    Cheers,
    Charles

    ReplyDelete
  2. Hello Charles, there are no javadocs. Ideally, you should work with the interface methods documented here: http://download.oracle.com/docs/cd/E12839_01/doc.1111/e14658/toc.htm
    Cheers,
    Andre.

    ReplyDelete
  3. Hi Andre, thanks, great post.
    Do you know how do get a list of users that are member of a LDAP group?
    thanks,
    Niko

    ReplyDelete
  4. Hi Andre, nice post.

    I have a question, I have setup my OpenLDAP authenticator like you mentioned and I have created my own object class in LDAP which extends inetorgperson and it also has a required attribute. I have added this class to jps-config.xml file using extended property user.object.classes and the required attribute using user.mandatory.attrs.
    Now when I try to add a user using this API I get the error saying " oracle.security.idm.IMException: Mandatory attribute missing :attributeName". Am I missing anything?

    Any help is greatly appreciated.

    Thanks,
    Ravi

    ReplyDelete
    Replies
    1. Ravi, this is being discussed internally. I still don't have an answer.
      Andre.

      Delete
  5. Hi,

    By any chance do you know how to change where the users are being created? How to assign and make it work the property user.create.bases?

    I can't specify where to create my users. It just create them in cn=Users,dc=com

    ReplyDelete
    Replies
    1. agtl,

      There are 3 properties that control where users are created:
      ST_SUBSCRIBER_NAME, RT_USER_CREATE_BASES and RT_USER_SELECTED_CREATE_BASE.

      RT_USER_SELECTED_CREATE_BASE is the create base where user will be created upon execution of createUser() call.
      If the selected create base is null and the ST_SUBSCRIBER_NAME is not specified then the value will be the first supplied value of the RT_USER_CREATE_BASE.
      If the ST_SUBSCRIBER_NAME is specified, the default value would be relative to subscriber name based on the identity store type.

      Try this before creating the user:

      JpsContextFactory ctxFactory = JpsContextFactory.getContextFactory();
      JpsContext ctx = ctxFactory.getContext();
      LdapIdentityStore idstoreService = (LdapIdentityStore)ctx.getServiceInstance(IdentityStoreService.class)
      IdentityStore idStore = idstoreService.getIdmStore();

      StoreConfiguration storeConfig = idStore.getStoreConfiguration();
      storeConfig.setProperty(OIDIdentityStoreFactory.RT_USER_SELECTED_CREATE_BASE,"cn=users,dc=us,dc=oracle,dc=com");

      Andre.

      Delete

Note: Only a member of this blog may post a comment.