Performing Privileged Operations in a TBO (One Approach)

Here is the scenario:  A contract management system has been implemented using Documentum.  The system employs elaborate roles and permission templates to ensure contract administrators can only manipulate their contracts. One task a contract administrator performs is changing the status of a contract.  A contract can be ‘pre-solicitation’, ‘in solicitation’, ‘awarded’, ‘canceled’, or ‘closed’.  Closed contracts are moved to a special folder only accessible to the contracts oversight manager and cannot be altered. If the contract administrator changes the status of a contract to ‘closed’, a TBO changes the contract ACL and moves it to the ‘closed contract’ folder.
The problem arises when the TBO tries to move the contract to the ‘closed contract’ folder where only the contracts oversight manager has read/write access.  The TBO (acting as the contract administrator) can’t move the contract because the contract administrator doesn’t have write permission in the ‘closed contract’ folder.  So, how can the TBO perform an operation that only a super user (or user with write permission on the ‘closed contracts’ folder) can perform?

I can think of two ways to make this happen:

  • Store the super user credentials in a properties file (encrypted of course) and have the TBO create a session using the super user’s credentials.  Then move the contract to the ‘closed contract’ folder using the super user session.  I rejected this idea because keeping the super user’s credentials in a properties file seemed like a configuration management and deployment headache (and I didn’t know the super user’s password).
  • Have a privileged process (like a server method that uses a trusted login) perform the move.  You could also have a job periodically scan for closed contracts and move them, but there could be a delay between the time the contract is actually closed and when it is moved. For this particular solution I chose to have a server method execute the move.

The following code snippet demonstrates how to call a server method from a TBO.  It uses the IDfSession.apply() method whose input arguments are not the most straightforward to understand or construct.

// Make sure you call super.save() before calling server method to move,
// otherwise all changes made in TBO are lost because the server method
// calls save on its version of obj. Also, re-fetch after call to server
// method if other updates need to be made in TBO. The i_vstamp will change.

IDfList args = new DfList();
IDfList datatypes = new DfList();
IDfList values = new DfList();

// construct first argument
args.appendString ("METHOD");
datatypes.appendString ("S");
values.appendString ("MoveToClosedFolder);   // this is the name of the server method to envoke

// construct second argument
args.appendString ("SAVE_RESULTS");
datatypes.appendString ("B");
values.appendString ("T");  // this will save any output generated by the server method to
                            //a file in the repo

// construct method arguments
args.appendString ("ARGUMENTS");
datatypes.appendString ("S");
values.appendString ("-docbase_name " + this.getSession().getDocbaseName() +
" -user_name " + this.getSession().getServerConfig().getString("r_install_owner") +
" -object_id " + this.getObjectId().toString() );

// call server method
IDfCollection col = this.getSession().apply("NULL","DO_METHOD", args, datatypes, values);
if (col != null)
col.close();

// refresh handle to object.
this.fetch(null);

The server method object (dm_method) was created in DA and specified to ‘Run as Owner’, ‘Use Method Server’, and NOT  ‘Launch Asynchronously’.  Just for completeness, here is the server method if you are interested.  I should mention that this method was written for Documentum 5.3.

public class MoveToClosedFolder implements IDmMethod {
  protected IDfSessionManager m_sessionMgr = null;
  protected IDfSession m_session = null;

  protected String m_docbase = null;
  protected String m_username = null;
  protected String m_password = null;
  protected String m_domain = null;
  protected String m_object_id = null;

  private static final String USER_KEY = "user_name";
  private static final String DOCBASE_KEY = "docbase_name";
  private static final String PASSWORD_KEY = "password";
  private static final String DOMAIN_KEY = "domain";
  private static final String OBJ_ID_KEY = "object_id";

  public static final String CLOSED_CONTRACT_FOLDER = "Closed Contracts";

  public int execute(Map params, PrintWriter pw) throws DfException {

    System.out.println("*** START MoveToClosedFolder Method ***");

    // get params
    initParams(params);

    // login and get session
    m_sessionMgr = login();
    m_session = m_sessionMgr.getSession(m_docbase);

    // get contract
    // Remember, this is a fresh version of the object and doesn't know anything about
    // the changes made to it in the TBO, unless the TBO saved the changes before it called
    // the method.  Conversely, the TBO needs to re-fetch this object after the method runs
    // to get the latest version.

    IDfSysObject sObj = (IDfSysObject) m_session.getObject(new DfId(m_object_id));
    if (sObj == null)
      throw new DfException("MoveToClosedFolder Method: sysobject to move is null");

    // get target folder
    IDfFolder target = (IDfFolder) m_session.getObjectByQualification("dm_folder where " +
      "object_name = '" + CLOSED_CONTRACT_FOLDER + "'");
    if (target == null)
      throw new DfException("MoveToClosedFolder Method: target folder is null");

    // get contract parent folder
    IDfFolder parent =  (IDfFolder) m_session.getObject(sObj.getFolderId(0));
    if (parent == null)
      throw new DfException("MoveToClosedFolder Method: parent folder is null");

    // move
    System.out.println("MoveToClosedFolder Method: moving " + sObj.getObjectName() +
      " (" + sObj.getObjectId().toString() + ") from " +
      parent.getFolderPath(0) + " to " + target.getFolderPath(0));

    sObj.link(target.getObjectId().toString());
    sObj.unlink(parent.getObjectId().toString());
    sObj.save();

    System.out.println("*** END MoveToClosedFolder Method ***");

    m_sessionMgr.release(m_session);
    return 0;
  }

  protected void initParams(Map params) throws DfException {

    Set keys = params.keySet();
    Iterator iter = keys.iterator();
    while (iter.hasNext()) {
      String key = (String) iter.next();
      if( (key == null) || (key.length() == 0) ) {
        continue;
    }

    String []value = (String[])params.get(key);

    if ( key.equalsIgnoreCase(USER_KEY) ) {
      m_username = (value.length > 0) ? value[0] : "";
      System.out.println(USER_KEY + " = " + value[0]);
    } else if ( key.equalsIgnoreCase(DOCBASE_KEY) ) {
      m_docbase = (value.length > 0) ? value[0] : "";
      System.out.println(DOCBASE_KEY + " = " + value[0]);
    } else if ( key.equalsIgnoreCase(PASSWORD_KEY) ) {
      m_password = (value.length > 0) ? value[0] : "";
      System.out.println(PASSWORD_KEY + " = " + value[0]);
    } else if ( key.equalsIgnoreCase(DOMAIN_KEY) ) {
      m_domain = (value.length > 0) ? value[0] : "";
      System.out.println(DOMAIN_KEY + " = " + value[0]);
    } else if ( key.equalsIgnoreCase(OBJ_ID_KEY) ) {
      m_object_id = (value.length > 0) ? value[0] : "";
      System.out.println(OBJ_ID_KEY + " = " + value[0]);
    } else {
      System.out.println("initParams: uncaught key: " + key + " = " + value[0]);
    }
  }

  protected IDfSessionManager login() throws DfException {
    if (m_docbase == null || m_username == null  ) {
      return null;
    }

    // now login
    IDfClient dfClient = DfClient.getLocalClient();
    if (dfClient != null) {
      IDfLoginInfo li = new DfLoginInfo();
      li.setUser(m_username);
      li.setPassword(m_password);
      li.setDomain(m_domain);
      IDfSessionManager sessionMgr = dfClient.newSessionManager();
      sessionMgr.setIdentity(m_docbase, li);
      return sessionMgr;
    }
    return null;
  }
}

Advertisements

About Scott
I have been implementing Documentum solutions since 1997. In 2005, I published a book about developing Documentum solutions for the Documentum Desktop Client (ISBN 0595339689). In 2010, I began this blog as a record of interesting and (hopefully) helpful bits of information related to Documentum, and as a creative outlet.

3 Responses to Performing Privileged Operations in a TBO (One Approach)

  1. Ivan says:

    What about using dynamic groups?

    First you add your dyn group with write perm. to the ACL in the ‘closed contract’ folder.

    Then your TBO before moving the content, adds that particular user to the dyn group. moves the content, and removes the user from the closed contract folder.

    Does it make sense?

    Like

    • Scott says:

      Ivan, that makes perfect sense. I haven’t had to deal with dynamic groups before, so that’s probably why I didn’t consider it when I was working this project. I think I will revisit this scenario in my spare time and re-work it to use dynamic groups. Thanks for the comment!

      Like

    • doquent says:

      Remember that dynamic groups do add an administration overhead – you need to add a member to the group in the regular manner before that member could be added dynamically.

      This overhead may be a challenge if there are a large number of diverse users who might be relevant to this scenario. On the other hand, if all the relevant users are in one group (directly or indirectly) this concern would be a non-issue.

      The other related concern is that what can be done in the TBO can be done outside it, given the ACL change. Has the security configuration been weakened from the originally desired one due to this ACL change? For example, an arbitrary document could be linked to this folder by members of the dynamic group even if they are not oversight managers.

      This concern probably applies to the approach discussed in this post as well. Can an inappropriate user invoke the server method and bypass the intended security?

      Once these concerns have been verified as acceptable, the dynamic group option proves to be a convenient one.

      Like

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

%d bloggers like this: