Dynamic groups. Advances. Part I

Previously I described how dynamic group capability work on RPC level. Now I’m going to describe how to apply that knowledge in DFC.

Use cases

Documentation

EMC suggests following dynamic group usecase (Administration and Configuration Guide):

You can use dynamic groups to model role-based security. For example, suppose you define a dynamic group called EngrMgrs. Its default membership behavior is to assume that users are not members of the group. The group is granted the privileges to change ownership and change permissions. When a user in the group accesses the repository from a secure application, the application can issue the session call to add the user to the group. If the user accesses the repository from outside your firewall or from an unapproved application, no session call is issued and Content Server does not treat the user as a member of the group. The user cannot exercise the change ownership or change permissions permits through the group.

How to implement this? Below is a webtop example:

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class DynamicGroupSessionManagerListener implements
        IDfSessionManagerEventListener {

    public DynamicGroupSessionManagerListener() {
        super();
    }

    @Override
    public void onSessionDestroy(IDfSession session) throws DfException {
        // never called
    }

    @Override
    public void onSessionCreate(IDfSession session) throws DfException {
        String clientId = ClientInfoService.getInfo().getIPAddress();
        // perform some ip-based checks
        if (isPotentialMember(session, "my_group")) {
            session.addDynamicGroup("my_group");
        }
    }

    /*
     * Here I coded a lot of checks because addDynamicGroup() method throws
     * exception if user does not belong to dynamic group instead of returning
     * true/false
     */
    protected boolean isPotentialMember(IDfSession session, String groupName)
        throws DfException {
        IDfGroup group = session.getGroup(groupName);
        if (group == null) {
            return false;
        }
        if (!group.getBoolean("is_dynamic")) {
            return false;
        }
        if (group.getBoolean("is_module_only")) {
            return false;
        }
        if (!(group.isGroupInGroup("dm_world") || group.isUserInGroup(session
                .getLoginUserName()))) {
            return false;
        }
        if (!group.getBoolean("is_protected")) {
            return true;
        }

        IPublicIdentity publicIdentity = new PublicIdentity();
        IClientRights clientRights = (IClientRights) session
                .getObjectByQualification("dm_client_rights where client_id='"
                        + publicIdentity.getIdentity() + "'");

        if (clientRights == null) {
            return false;
        }

        if (clientRights.isAllowAllRoles()) {
            return true;
        }

        return clientRights.findString("allowed_roles", groupName) > -1;
    }

}

app.xml:

<config>
 <scope>
  <application>
....
   <dfsessionmanagereventlistener>
       <class>com.tld.DynamicGroupSessionManagerListener</class>
   </dfsessionmanagereventlistener>
.....
  </application>
 </scope>
</config>

Unfortunately such setup does not work in multidocbase environment: IDfSession.addDynamicGroup() method not only sends SET_DYNAMIC_GROUPS RPC but also modifies SessionManager config, that causes exception if dynamic groups differ across repositories:

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws Exception {
        IDfSessionManager sessionManager = new DfClientX().getLocalClient()
                .newSessionManager();
        sessionManager
                .setIdentity("repo1", new DfLoginInfo("test01", "test01"));
        sessionManager
                .setIdentity("repo2", new DfLoginInfo("test01", "test01"));
        IDfSession session1 = sessionManager.getSession("repo1");
        session1.addDynamicGroup("test_dyn_group");
        IDfSessionManagerConfig config = sessionManager.getConfig();
        for (int i = 0, n = config.getAddedDynamicGroupCount(); i < n; i++) {
            System.out
                    .println("group added: " + config.getAddedDynamicGroup(i));
        }
        IDfSession session2 = sessionManager.getSession("repo2");
    }

}

result:

group added: test_dyn_group
Exception in thread "main" DfServiceException:: THREAD: main; MSG: 
   [DM_SESSION_E_SETUP_ROLES_FOR_RPC]error:  "Error initializing dynamic roles 
   for RPC test_dyn_group ({1})."; ERRORCODE: 100; NEXT: null
	at com.documentum.fc.client.impl.docbase.DocbaseExceptionMapper.newException(DocbaseExceptionMapper.java:57)
	at com.documentum.fc.client.impl.connection.docbase.MessageEntry.getException(MessageEntry.java:39)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseMessageManager.getException(DocbaseMessageManager.java:137)
	at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.checkForMessages(NetwiseDocbaseRpcClient.java:310)
	at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.applyForBool(NetwiseDocbaseRpcClient.java:354)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection$1.evaluate(DocbaseConnection.java:1175)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.evaluateRpc(DocbaseConnection.java:1113)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.applyForBool(DocbaseConnection.java:1168)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.apply(DocbaseConnection.java:1153)
	at com.documentum.fc.client.impl.docbase.DocbaseApi.setDynamicGroups(DocbaseApi.java:1709)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.initializeDynamicGroups(DocbaseConnection.java:540)
	at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.authenticate(DocbaseConnection.java:431)

There are two options to resolve such issue:

  • Keep dynamic groups (and client rigths) in sync across all repositories
  • Do not use addDynamicGroup method and code some utility methods instead

First option does not prevent another weird behaviour of SessionManager – modification of SessionManager config causes SessionManager to return another session upon next call of getSession() method:

/**
 * @author Andrey B. Panfilov <andrew@panfilov.tel>
 */
public class Test {

    public static void main(String[] args) throws Exception {
        IDfSessionManager sessionManager = new DfClientX().getLocalClient()
                .newSessionManager();
        sessionManager.setIdentity("repo01",
                new DfLoginInfo("test01", "test01"));
        IDfSession session1 = sessionManager.getSession("repo01");
        session1.beginTrans();
        session1.addDynamicGroup("test_dyn_group");
        System.out.println("transaction active: "
                + session1.isTransactionActive());
        IDfSession session2 = sessionManager.getSession("repo01");
        System.out.println("transaction active: "
                + session2.isTransactionActive());
    }

}

result:

transaction active: true
transaction active: false

I have tried to implement second option using RPC commands directly:

public static boolean addDynamicGroup(IDfSession session, String groupName)
        throws DfException {
    DocbaseApi api = ((ISession) session).getDocbaseApi();
    List<String> groups = new ArrayList<String>();
    ITypedDataIterator iterator = null;
    try {
        iterator = api.getDynamicGroups();
        while (iterator.hasNext()) {
            ITypedData data = iterator.next();
            if (data.hasAttr("group_name")) {
                groups.add(data.getString("group_name"));
            }
        }
    } finally {
        if (iterator != null) {
            iterator.close();
        }
    }
    if (groups.contains(groupName)) {
        return false;
    }
    groups.add(groupName);
    api.setDynamicGroups(groups, new ArrayList<String>(), true, false);
    return true;
}

but found out that such solution does not properly handle timeouts and disconnects (when reconnect occurs new server-session does know nothing about client’s dynamic group and client should restore state by sending SET_DYNAMIC_GROUPS RPC again):

public static void main(String[] args) throws Exception {
    IDfSessionManager sessionManager = new DfClientX().getLocalClient()
            .newSessionManager();
    sessionManager.setIdentity("repo01",
            new DfLoginInfo("test01", "test01"));
    IDfSession session = sessionManager.getSession("repo01");
    if (addDynamicGroup(session, "test_dyn_group")) {
        System.out.println("added");
    }
    IDfQuery query = new DfQuery(
            "SELECT user_name FROM dm_user WHERE user_name=USER");
    query.execute(session, IDfQuery.DF_EXEC_QUERY).close();
    Thread.sleep(1000 * 60 * 30);
    // here session transparently reconnects
    query.execute(session, IDfQuery.DF_EXEC_QUERY).close();
    if (addDynamicGroup(session, "test_dyn_group")) {
        System.out.println("added");
    }
}

result:

added
added

So, after 10 years of introducing SET_DYNAMIC_GROUPS RPC is still unusable or has very limited usage 😦

One thought on “Dynamic groups. Advances. Part I

  1. Pingback: Dynamic groups. Advances. Part II | Documentum in a (nuts)HELL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s