An Architect's View

CFML, Clojure, Software Design, Frameworks and more...

An Architect's View

Better Living Through Transfer and ColdSpring

August 15, 2008 ·

We have a live system with customer data and a new requirement comes along that a particular piece of customer data must be encrypted in the database from now on. We already encrypt some columns (using Triple DES - which you might have guessed given the recent posts on my blog and here about mimicking ColdFusion's encryption in Java/Groovy). What is the smallest possible code change to ensure that as any user updates their data in future, this item will automatically be encrypted - whilst still handling the case of legacy data being unencrypted? We use Transfer for all our persistent business objects and almost all of our business objects have a decorator defined (for validation or some additional business logic). We also use Brian's TDOBeanInjectorObserver to automatically inject services into our business objects - just add a setter for a service and the bean injector takes care of the rest. Here's the bean injector definition in our ColdSpring file:
<bean id="transferObjectInjector" class="coldspring.transfer.TDOBeanInjectorObserver">
   <constructor-arg name="transfer"><ref bean="transfer" /></constructor-arg>
   <constructor-arg name="suffixList"><value>service,datasource</value></constructor-arg>
   <constructor-arg name="debugMode"><value>true</value></constructor-arg>
</bean>
Normally you would declare it non-lazy but we already do other non-lazy initialization so in our ColdSpring factory initialization code, we do this:
<cfset bf.getBean("transferObjectInjector") />
to force the bean injector to be initialized which, in turn, registers itself as a Transfer event listener (so that it can intercept object creation). The suffixList specifies that any set*Service() method or set*Datasource() method on the business objects managed by Transfer should be matched to beans defined in ColdSpring and injected. So how do we add the on-demand encryption to our business object's data?First, we add a setter to the Transfer object decorator (our business object) to match our encryption service:
function setEncryptionService(encryptionService) {
   variables.encryptionService = encryptionService;
}
(Yes, I use cfscript a lot these days) Then we override the setter for the data we need to encrypt:
function setSFPassword(password) {
   getTransferObject().setSFPassword( variables.encryptionService.encryptStr(password) );
}
This uses the (injected) encryption service to encrypt the (user-submitted) password and then pass it to the underlying setter (generated by Transfer). That was easy. But what about the getter? That has to handle unencrypted data as well as encrypted data. Here's the matching setter:
function getSFPassword() {
   var password = getTransferObject().getSFPassword();
   var decrypted = "";
         
   try {
      decrypted = variables.encryptionService.decryptStr(password);
      if (decrypted == "") decrypted = password;
      return decrypted;
   } catch (any ex) {
      // must be unencrypted already       return password;
   }
}
We use the underlying getter to retrieve the value from the database and then we try to decrypt it. If decryption fails, we assume the data is currently unencrypted and return it as-is. Note that decryption can fail in two ways: it can throw an exception or it can return an empty string! This allows us to encrypt data upon any user updates while still handled unencrypted legacy data during the migration period.

Tags: coldfusion · coldspring · orm

0 responses