Enhancing document security with FileNet Web Application Toolkit

This article examines a scenario for encoding and decoding a document to enhance security using the FileNet Web Application Toolkit to customize IBM® FileNet® P8 Workplace. It also includes a look at the toolkit architecture in the context of saving a document.

Bharath N S (bnadampa@in.ibm.com), Senior Staff Software Engineer, IBM  

Photo of Bharath NadampalliBharath N.S. works as a Senior Staff Software Engineer at IBM India Software labs, with over ten years of experience in software development experience based on Microsoft technologies, and more recently, on Java and J2EE technologies. Bharath's hobbies include image processing and exploring new technologies.



Nandish S. Kori (nandkori@in.ibm.com), Software Engineer, IBM

Nandish KoriNandish S Kori works as a Software Engineer at IBM India Software lab on the FileNet team. He has 4 years of software development experience based on Java and C. His hobbies include playing any game and exploring new things.



17 February 2011

Introduction

In a typical content management or process management system, documents are routed from one user to the next with no guarantee that the contents of the document are secure. After it has been downloaded or checked out, a document might be used maliciously. So let us say we encrypt the document as it comes from the content repository, for example FileNet Content Engine (CE). The document cannot be openly viewed, even if it is tranferred maliciously. It will be viewable only by users who can decrypt it with the help of a policy server.

A typical use case for a digital rights management (DRM) scenerio is shown in Figure 1:

Figure 1. Typical digital rights management scenario
Diagram shows flow from user request to decoding the document, to repository, to encoding, to user request, to user role database, to decoding the document, and then back to user request

The scenario includes the following steps:

  1. First you (the user) request to check in a protected document(Base64 encoding and decoding is used in this example).
  2. The document, which needs to be sent to a repository (in this case FileNet Content Engine), must be decoded to enable text search.
  3. When you attempt to check-out or download the file, the system encrypts the document, then sends it to you.
  4. You then contact the policy server provided by a DRM solution provider with the checked-out, encrypted file. After you authenticate at the policy server, the file is decrypted and you can view or modify it as desired.

You will need to customize FileNet P8 in order to accomplish steps 2 and 3.

The solution uses FileNet Web Application Toolkit (WAT), an extensible framework for building web applications that was used to create the FileNet P8 Workplace.


Prerequisites

To implement this solution, you should have some knowledge of the different components of the IBM FileNet P8 platform,including Application Engine (AE), Content Engine (CE) and Process Engine (PE). The code accompanying this article has been tested on FileNet P8 4.5.1 and IBM WebSphere Application Server - ND, 6.1.0.25. In addition, you should have familiarity with Eclipse and Java.


Next steps

How do you achieve the customizations shown in Figure 1? This will depend on which method was used to get the document into the FileNet CE repository:

  • Using FileNet P8 Workplace (Application Engine)
  • Using FileNet Enterprise Manager (FEM)
  • Using a custom application

In the next part of the article, you'll see how to customize the FileNet P8 Workplace so that you can do the following operations:

  • Decrypt an encrypted file while adding through FileNet P8 Workplace (WAT).
  • Encrypt a file while viewing or checking out the file using FileNet P8 Workplace (WAT).

Solving the business case

In the real world there are many third-party vendors who offer DRM solutions. Most of them use an architecture similar to the one you saw in Figure 1.

For the sake of simplicity, we used Base64 in our example to encode and decode a document. In this article, we are considering simple Text files (*.txt). In an actual content management environment, there are be many ways in which document protection can be achieved, for all kinds of documents.

We are using Apache Base64 conversion library commons-codec-1.4.jar (org.apache.commons.codec.binary.Base64)

Document check-in

Document check-in in FileNet P8 Workplace happens through WcmUploadInputStream servlet. This is part of the p8ToolKit.jar, found at the Websphere Application Server machine where the workplace is deployed, in the following path:
<WAS node Cell path>\ Workplace.ear\app_engine.war\WEB-INF\lib\

You will find the WcmUploadInputStream.java file at this path:
<WAS node Cell path>\Workplace.ear\app_engine.war\AESource

It is part of the com.filenet.wcm.toolkit.server.util package.

WcmUploadInputStream class has a static method getInstance which is overloaded twice:

  • The first overload takes HttpServletRequest and the form name as second and third parameters respectively.
  • The second overload, shown below, takes the WcmMimeFormParser and HttpServletRequest as second and third parameters respectively.

WcmDataStore is the common parameter for both of the overloaded functions. This parameter holds the user session-specific data store.

As the code examples below illustrate the following steps:

  • A static function DecodeFile is added to WcmUploadInputStream.java. This function takes a reference to file object and the filepath where the inputstream has been cached.
  • The DecodeFile is called in getInstance.
Listing 1. Document check-in
public static WcmUploadInputStream getInstance(WcmDataStore ds,
	WcmMimeFormParser mfp,HttpServletRequest req)throws IOException,Exception
    {
	WcmUploadInputStream uis   = null;
        if ( !mfp.isEOD() )
        {
            String fname = mfp.getSectionParameter(WcmMimeFormParser.INPUT_FILENAME);
            if ( fname != null )
            {
                File theFile = null;
                try
                {
		theFile = UploadDownloadFileUtil.initUploadCacheFile(req.getLocale(),
			req.getSession().getServletContext(),null,null);
                }
                catch (WcmException e)
                {
                	//System.out.println("Hello #2_3");
                    throw new IOException(wsCacheFileError.toString(req.getLocale()));
                }
		System.out.println("File name is : " + fname);
		System.out.println("File name is : " + theFile.getPath());

		FileOutputStream  fos     = new FileOutputStream(theFile);
		mfp.writeSectionContent(fos);
		fos.close();
		// Start of decoding
                 File theFile1 = DecodeFile (theFile,fname,req.getLocale());
                 //End of decoding

                uis = new WcmUploadInputStream(
                    new FileInputStream(theFile1),
                    theFile1,
                    mfp.getSectionContentLength(),
                    mfp.getSectionParameter(WcmMimeFormParser.MIME_TYPE),
                    //fname,
                    theFile1.getPath(),
                    theFile1.getPath(),
                    true, true, req.getSession().getServletContext());
            }
        }

        return(uis);
    }
*******************************************************************************

public static File DecodeFile(File theFile,String fileName2,Locale locale) 
throws Exception
  {
	  String fname = theFile.getPath();
	  FileInputStream fis1 = new FileInputStream(fname); 
	  String fileName2_1= fileName2.substring(fname2+1,  fileName2.length()); 
	  fileName2 = "C:\\TEMP\\"+fileName2_1;
	  File theFile1 =  new File(fileName2);
	  FileOutputStream fos1 = new FileOutputStream (theFile1);
	  int off1 =0;
	  int bytes_read1;
	  byte [] buffer1 = new byte[4096];
	  while ((bytes_read1 = fis1.read(buffer1))!= -1)
	  {
	 	//byte[] decoded = Base64.decodeBase64(buffer1);
		byte[] encoded = WcmEncodingUtil.encodeBase64(locale,nextBytes);
		fos1.write(decoded, off1, decoded.length);	
	}
	fos1.close();
	fis1.close();
	System.out.println("filename2 : "+fileName2);
	return theFile1;
  }

Once the code changes are made, it is time to deploy the code into the workplace following these steps:

  1. Compile the Java file.
  2. Deploy the .jar file (in this case p8Toolkit.jar) to the folder where Workplace is deployed

Once the WcmGetContentServlet.java is compiled, it creates two class files. Copy them to the appropriate folders and then update the p8toolkit.jar file with both the class files, using these commands:

  1. Copying the class file to the appropriate folder:
    copy /Y WcmGetContentServlet.class .\com\filenet\wcm\toolkit\server\servlet\
  2. Updating the jar with the new class file:
    jar -uvf p8toolkit.jar com\filenet\wcm\toolkit\server\servlet\WcmUploadInputStream.class
  3. Deploying the jar file back into the application:
    copy /Y p8toolkit.jar "C:\Program Files\IBM\WebSphere\AppServer\profiles\AppSrv01\installedApps\idmdev18bNode01Cell\Workplace.ear\app_engine.war\WEB-INF\lib\

Note that all these operations are performed in the AESource directory:
< WAS node Cell path>\Workplace.ear\app_engine.war\AESource

Document open and check-out

Open and check-out in FileNet P8 Workplace occurs with the WcmGetServlet.java file, part of the com.filenet.wcm.toolkit.server.servlet package. This is the layer where you manipulate the filestream before sending it the user.

The function in question is streamContentToBrowser. This particular function makes a call to a function called transferHeaders. The response object is sent to this function as shown in the code snippet below.

The transferHeaders function uses this request object and sets the contentLength of the response object. Our implementation uses Base64 encoding, which increases the size of the encoded file. So we get the stream encoded, and also calculate the encoded stream length, which is used in the transferHeaders function call.

The encoded stream is sent along the previous normal path. That is, we use it the tranferContent function call, which writes it to the response stream

So that is the end of document open / check-out.

Listing 2. Document open/check-out
private void streamContentToBrowser(Document doc) throws IOException
  {

      TransportInputStream inStream = doc.getContentElement(iElement);

      response.setHeader("ResultXml", "<?xml version=\"1.0\"?><response>
	  <errorcode>0</errorcode><description>" +
                "Success</description></response>");

	String filename = inStream.getFilename();
      contentLength = (int) inStream.getContentSize();
      sMimeType = inStream.getMimeType();

  if (sMimeType != null && sMimeType.equalsIgnoreCase(MimeTypes.FILENET_PUBLISHTEMPLATE))
                sMimeType = "text/xml";

      if (contentLength <0 && getEnableFiletracking(dataStore, 
	  WcmStringResources.getClientLocale(request)))
      {
        contentLength= getContentLength(inStream);
        nStream = doc.getContentElement(iElement);
	  }
      ServletOutputStream outStream = response.getOutputStream();
		

	  // Start of encoding
                 ByteArrayOutputStream outStream_Encoded = EncodeFile (inStream);
                 //End of encoding
 ByteArrayInputStream inS_en = new ByteArrayInputStream(outStream_Encoded.toByteArray());
      contentLength=  encodedContentLength;
      transferHeaders(filename, contentLength, sMimeType, response);

      //transferContent(inStream, outStream);
      transferContent(inS_en, outStream);
      outStream.close();
      inStream.close();
      inS_en.close();
 }
public static ByteArrayOutputStream EncodeFile(TransportInputStream inStream) 
		throws Exception
  {
	  InputStream istream = inStream;
      int nBytesRead = 0;
      encodedContentLength=0;
      ByteArrayOutputStream outStream_Encoded= new ByteArrayOutputStream();
      OutputStream outFile = new FileOutputStream ("C:\\File_instream.txt");
      for (byte[] nextBytes = new byte[8192]; nBytesRead != -1; 
	  nBytesRead = inStream.read(nextBytes))
      {
		if(nBytesRead>0)
		{
			//byte[] encoded = Base64.encodeBase64(nextBytes);
			byte[] encoded = WcmEncodingUtil.encodeBase64(nextBytes);
			encodedContentLength=encodedContentLength+encoded.length;
			outStream_Encoded.write(encoded, 0, encoded.length);
			//WriteToLog(encoded);
			outFile.write(encoded, 0, encoded.length);
		}
      }
      outFile.close();
	return outStream_Encoded;
  }

Our solution leveraged the org.apache.commons.codec.binary.Base64 package for Base64 encoding and decoding. Refer to the Resources section for more information on the package. Alternatively you could use the WcmEncodingUtil class which has methods such as WcmEncodingUtil.encodeBase64 and WcmEncodingUtil.decodeBase64. This class can be used for the same purpose.


Understanding the underlying architecture

Some aspects of how the FileNet Web Application Toolkit works played an important part in solving this problem, spcifically the call sequence with respect to the save document function.

Figure 2 illustrates the model-view-controller (MVC) pattern used in WAT. For more information, see the FileNet documentation.

Figure 2. Sequence diagram
Sequence diagram

The modules belonging to each part are as follows:

  • Model:
    • WcmSaveOperation is part of the com.filenet.wcm.apps.server.ui.operation package. This will call the appropriate data provider to do any operation on CE or PE. This class also implements the events that are called by the controller. In this case it implements the onSave event. (Also this derives ultimately from a base class called WcmUiModule)
    • WcmAuthoringDataProvider is part of the com.filenet.wcm.toolkit.server.dp package. The primary job of a data provider is to use to the CE and PE apis. In this implementation, the authoring data provider talks to CE to retrieve, add, or replace documents in CE.
  • View:
    • WcmSaveContent.jsp is found at "app_engine.war\UI-INF\jsp\ui\operations" folder in websphere's workplace deployment folder. It renders the HTML output the client. The HTML content is built in the model phase. This page is called from the model phase via a redirect.
  • Controller:
    • WcmSaveContent.jsp is found at "app_engine.war\operations" folder in WebSphere's workplace deployment folder. This page configures the controller. During the configuration, events that needs to be called in the model are recovered from the request object.
    • The controller registers the modules in the model phase. During the registration it calls the Initialize method on each of the modules. In this example it will be WcmSaveOperation and WcmAuthoringDataProvider.
    • Once the initialization operations on the modules are complete, the controller calls the handleEvent which triggers the events. In this case it is the onSave event on WcmSaveOperation object.

Conclusion

This article was aimed at solving a potential security issue in a document management scenario, and also explored a piece of the Web Application Toolkit architecture. There could be scenarios where you don't want to encode and decode some of the documents in your workflow. This is an implementation detail that you can figure out how to implement when you go through WcmUploadInputStream.

Note that the article explains only a partial overview sequence of save document. There are many other modules that are involved, such as WcmUploadInputStream which we have used to implement the business case. The information in this article provided a beginning as you start to explore this topic, and a base for further exploration.


Thanks to Phong Chu, Technical Instructor Specialist, ECM Education Services, for reviewing this article and providing valuable comments.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Information management on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Information Management
ArticleID=627297
ArticleTitle=Enhancing document security with FileNet Web Application Toolkit
publish-date=02172011