Sencha: Charlotte User Group Meeting

Yesterday we had our first Sencha Charlotte Usergroup Meeting. It turned out to be productive discussions around what, when, and how we want to meet.
We also were able to put more meat on the bone for future presentation. Thus, this is shaping up to be a good group and I am looking forward to learn new and interesting things.
Currently we have meetings scheduled until September. Here are the topics that we intend to cover:

  • 5/31 – Expand Designer 2 Demo – Bilal
  • 6/28 – PhoneGap – Jojo
  • 7/26 – MVC in JS (Backbone, Sencha) – Joe 
  • 8/30 – Unit Testing w. Jasmin/ QUnit   –  Pramod
  • 9/27 – Sencha GWT   –  Brad

More detail for each of these meetings is available on our group meetup site.

Cheers,
B.

CF: When does OnRequestEnd get executed

This is one is from my main man Kip (@kipthegreat) he ran through this exercise using Adoce CF 9.
Might be helpful for others to know.

Situation
Does OnRequestEnd() get
executed?
Page calls <cfabort> tag
Yes
Page calls to redirect
user
Yes
Page calls with
abort=”true”
Yes
Request exceeds timeout
NO
Uncaught exception
NO

The lesson to pay attention to is to catch and handle your exceptions ;o)

 Cheers,
-B

JavaScript: Check whether a sub object is defined in a multi-object chain

When you are in JavaScript mode it is common for you to use
typeof operator to see whether a given variable, object, function etc. is known to JavaScript.

However, many times I find myself writing more complex code because it is a child of a child object that I need to check for a value. This makes for ugly code.
Being rather simple minded I chose to Google for an obvious solution. Unfortunately, nothing was immediately available (or I might be simple missing it). Most people seem to only need to be dealing with the top level object deceleration and, thus, no need for anything else.

So, to make a long story short, I created a simple helper function that does most of the work for me and cleans up the repetitive code. Feel free to use it at your leisure:

/**

 * Take string input in varName and determine whether it is defined object in Javascript

 * @param {String} varName

 * @return {boolean}

 * @author Bilal Soylu

 */

function isDefined(varName) {

 var retStatus = false;

 

 if (typeof varName == "string") {

  try {

   var arrCheck = varName.split(".");

   var strCheckName = "";

   for (var i=0; i < arrCheck.length; i++){

    strCheckName = strCheckName + arrCheck[i];

    //check wether this exist

    if (typeof eval(strCheckName) == "undefined") {

     //stop processing

     retStatus = false;

     break;     

    } else {

     //continue checking next node

     retStatus = true;

     strCheckName = strCheckName + ".";

    }

   }

  } catch (e) {

   //any error means this var is not defined

   retStatus = false;

  }

  

 } else {   

  throw "the varName input must be a string like myVar.someNode.anotherNode[]";

 }

 

 return retStatus;

} 

Cheers,
-B.

Java: Implementing PGP Single Pass Sign and Encrypt using League of Bouncy Castle library

The League of Bouncy Castle Cryptography library is chock-full of goodies but it is hard to convert what is in there to more practical examples.
The example files are a solid basis but I seam to need to fiddle quite a bit until it something is usable for me. The PGP Single Pass Sign and Encrypt process is one of these things that took me for a long time to figure out. I owe much of the actual solution impementation to John Opincar who solved this puzzle for C#.
Here is my implementation for Java:



/**
 * 
 */
package net.boncode.crypto;

//bouncy castle imports
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;

//java imports
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Iterator;

/**
 * @author Bilal Soylu
 * 
 */

public class OnePassSignatureProcessor {

 /**
  * This is the primary function that will create encrypt a file and sign it
  * with a one pass signature. This leans on an C# example by John Opincar
  * @author Bilal Soylu
  * @param targetFileName
  *            -- file name on drive systems that will contain encrypted content
  * @param embeddedFileName
  *            -- the original file name before encryption
  * @param secretKeyRingInputStream
  *            -- Private Key Ring File
  * @param targetFileStream
  *            -- The stream for the encrypted target file
  * @param secretKeyPassphrase
  *            -- The private key password for the key retrieved from
  *            collection used for signing
  * @param signPublicKeyInputStream
  *            -- the public key of the target recipient to be used to
  *            encrypt the file
  * @throws Exception
  */
 public void fEncryptOnePassSignatureLocal(String targetFileName,
   String embeddedFileName, InputStream secretKeyRingInputStream,
    OutputStream targetFileStream, String secretKeyPassphrase,   
   InputStream signPublicKeyInputStream, InputStream contentStream) throws Exception {
  // ** INIT
  // read public Key from stream (file, if keyring we use the first working key)
  PGPPublicKey encKey = readPublicKey(signPublicKeyInputStream);
  // need to convert the password to a character array
  char[] password = secretKeyPassphrase.toCharArray();
  int BUFFER_SIZE = 1 << 16; // should always be power of 2(one shifted bitwise 16 places)
  //for now we will always do integrity checks and armor file
  boolean armor = true;
  boolean withIntegretyCheck = true;
  //set default provider, we will pass this along
  BouncyCastleProvider bcProvider = new BouncyCastleProvider();

  // armor stream if set
  if (armor)
   targetFileStream = new ArmoredOutputStream(targetFileStream);

  // Init encrypted data generator
  PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(
    SymmetricKeyAlgorithmTags.CAST5, withIntegretyCheck,
    new SecureRandom(), bcProvider);
  encryptedDataGenerator.addMethod(encKey);
  OutputStream encryptedOut = encryptedDataGenerator.open(targetFileStream,new byte[BUFFER_SIZE]);

  // start compression
  PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(
    CompressionAlgorithmTags.ZIP);
  OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);

  //start signature
  //PGPSecretKeyRingCollection pgpSecBundle = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(secretKeyRingInputStream));
  //PGPSecretKey pgpSecKey = pgpSecBundle.getSecretKey(keyId);
  PGPSecretKey pgpSecKey = readSecretKey(secretKeyRingInputStream);
  if (pgpSecKey == null)
   throw new Exception("No secret key could be found in specified key ring collection.");
  PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(password,bcProvider);

  PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
    pgpSecKey.getPublicKey().getAlgorithm(),
    HashAlgorithmTags.SHA1, bcProvider);
  
  signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
  // iterate to find first signature to use
  for (@SuppressWarnings("rawtypes")
  Iterator i = pgpSecKey.getPublicKey().getUserIDs(); i.hasNext();) {
   String userId = (String) i.next();
   PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
   spGen.setSignerUserID(false, userId);
   signatureGenerator.setHashedSubpackets(spGen.generate());
   // Just the first one!
   break;
  }
  signatureGenerator.generateOnePassVersion(false).encode(compressedOut);

  // Create the Literal Data generator output stream
  PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
  // get file handle
  File actualFile = new File(targetFileName);
  // create output stream
  OutputStream literalOut = literalDataGenerator.open(compressedOut,
    PGPLiteralData.BINARY, embeddedFileName,
    new Date(actualFile.lastModified()), new byte[BUFFER_SIZE]);
  
  
  // read input file and write to target file using a buffer
  byte[] buf = new byte[BUFFER_SIZE];
  int len;
  while ((len = contentStream.read(buf, 0, buf.length)) > 0) {
   literalOut.write(buf, 0, len);
   signatureGenerator.update(buf, 0, len);
  }
  // close everything down we are done
  literalOut.close();
  literalDataGenerator.close();
  signatureGenerator.generate().encode(compressedOut);
  compressedOut.close();
  compressedDataGenerator.close();
  encryptedOut.close();
  encryptedDataGenerator.close();
  

  if (armor) targetFileStream.close();

 }
 /**
  * Try to find a public key in the Key File or Key Ring File
  * We will use the first one for now.
  * @author Bilal Soylu
  * @param in -- File Stream to KeyRing or Key
  * @return first public key
  * @throws IOException
  * @throws PGPException
  */
 private static PGPPublicKey readPublicKey(InputStream in)
   throws IOException, PGPException {
  in = PGPUtil.getDecoderStream(in);

  PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);

  //
  // we are only looking for the first key that matches
  //

  //
  // iterate through the key rings.
  //
  Iterator rIt = pgpPub.getKeyRings();

  while (rIt.hasNext()) {
   PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
   Iterator kIt = kRing.getPublicKeys();

   while (kIt.hasNext()) {
    PGPPublicKey k = (PGPPublicKey) kIt.next();

    if (k.isEncryptionKey()) {
     return k;
    }
   }
  }

  throw new IllegalArgumentException(
    "Can't find encryption key in key ring.");
 }

 
 
 /**
  * Find first secret key in key ring or key file. 
  * A secret key contains a private key that can be accessed with a password.
  * @author Bilal Soylu
  * @param in -- input Key file or key ring file
  * @param passwd -- password for key
  * @return matching private key
  * @throws IOException
  * @throws PGPException
  * @throws NoSuchProviderException
  */
 private static PGPSecretKey readSecretKey(InputStream in)
   throws IOException, PGPException, NoSuchProviderException {
  
  PGPSecretKey               sKey = null;
  try {
   in = PGPUtil.getDecoderStream(in);
   PGPSecretKeyRingCollection pgpPriv = new PGPSecretKeyRingCollection(in);
 
   // we just loop through the collection till we find a key suitable for
   // decrypt
   Iterator  it = pgpPriv.getKeyRings();       
   PGPSecretKeyRing   pbr = null;
 
         while (sKey == null && it.hasNext())
         {
          Object readData = it.next();
          if (readData instanceof PGPSecretKeyRing) {           
           pbr = (PGPSecretKeyRing)readData;             
              sKey =  pbr.getSecretKey();
             }
         }
         
         if (sKey == null)
         {
             throw new IllegalArgumentException("secret key for message not found.");
         }
  }
        catch (PGPException e)
        {
            System.err.println(e);
            if (e.getUnderlyingException() != null)
            {
                e.getUnderlyingException().printStackTrace();
            }
        }
        return sKey; 
 } 
 
 

 /**
  * fDecryptOnePassSignature will decrypt a file that was encrypted using
  * public key, then signed with a private key as one pass signature based on
  * example of verifyAndDecrypt() by Raul
  * 
  * @param encryptedInputStream
  * @param signPublicKeyInputStream
  * @param secretKeyInputStream
  * @param secretKeyPassphrase
  * @return
  * @throws Exception
  */
 public void fDecryptOnePassSignatureLocal(InputStream encryptedInputStream,
   InputStream signPublicKeyInputStream,
   InputStream secretKeyInputStream, String secretKeyPassphrase,
   OutputStream targetStream) throws Exception {

  Security.addProvider(new BouncyCastleProvider());

  // The decrypted results.
  // StringBuffer result = new StringBuffer();
  // The private key we use to decrypt contents.
  PGPPrivateKey privateKey = null;
  // The PGP encrypted object representing the data to decrypt.
  PGPPublicKeyEncryptedData encryptedData = null;

  // Get the list of encrypted objects in the message. The first object in
  // the
  // message might be a PGP marker, however, so we skip it if necessary.
  PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(encryptedInputStream));
  Object firstObject = objectFactory.nextObject();
  System.out.println("firstObject is " + firstObject);
  PGPEncryptedDataList dataList = (PGPEncryptedDataList) (firstObject instanceof PGPEncryptedDataList ? firstObject
    : objectFactory.nextObject());

  // Find the encrypted object associated with a private key in our key
  // ring.
  @SuppressWarnings("rawtypes")
  Iterator dataObjectsIterator = dataList.getEncryptedDataObjects();
  PGPSecretKeyRingCollection secretKeyCollection = new PGPSecretKeyRingCollection(
    PGPUtil.getDecoderStream(secretKeyInputStream));
  while (dataObjectsIterator.hasNext()) {
   encryptedData = (PGPPublicKeyEncryptedData) dataObjectsIterator.next();
   System.out.println("next data object is " + encryptedData);
   PGPSecretKey secretKey = secretKeyCollection.getSecretKey(encryptedData.getKeyID());
   
   if (secretKey != null) {
    // This object was encrypted for this key. If the passphrase is
    // incorrect, this will generate an error.
    privateKey = secretKey.extractPrivateKey(secretKeyPassphrase.toCharArray(), "BC");
    break;
   }
  }

  if (privateKey == null) {
   System.out.println();
   throw new RuntimeException("secret key for message not found");
  }

  // Get a handle to the decrypted data as an input stream
  InputStream clearDataInputStream = encryptedData.getDataStream( privateKey, "BC");
  PGPObjectFactory clearObjectFactory = new PGPObjectFactory( clearDataInputStream);
  Object message = clearObjectFactory.nextObject();

  System.out.println("message for PGPCompressedData check is " + message);

  // Handle case where the data is compressed
  if (message instanceof PGPCompressedData) {
   PGPCompressedData compressedData = (PGPCompressedData) message;
   objectFactory = new PGPObjectFactory(compressedData.getDataStream());
   message = objectFactory.nextObject();
  }

  System.out.println("message for PGPOnePassSignature check is " + message);

  PGPOnePassSignature calculatedSignature = null;
  if (message instanceof PGPOnePassSignatureList) {
   calculatedSignature = ((PGPOnePassSignatureList) message).get(0);
   PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(
     PGPUtil.getDecoderStream(signPublicKeyInputStream));
   PGPPublicKey signPublicKey = publicKeyRingCollection
     .getPublicKey(calculatedSignature.getKeyID());
   calculatedSignature.initVerify(signPublicKey, "BC");
   message = objectFactory.nextObject();
  }

  System.out.println("message for PGPLiteralData check is " + message);

  // We should only have literal data, from which we can finally read the
  // decrypted message.
  if (message instanceof PGPLiteralData) {
   InputStream literalDataInputStream = ((PGPLiteralData) message).getInputStream();
   int nextByte;

   while ((nextByte = literalDataInputStream.read()) >= 0) {
    // InputStream.read guarantees to return a byte (range 0-255),
    // so we
    // can safely cast to char.
    calculatedSignature.update((byte) nextByte); // also update
                // calculated
                // one pass
                // signature
    // result.append((char) nextByte);
    // add to file instead of StringBuffer
    targetStream.write((char) nextByte);
   }
   targetStream.close();
  } else {
   throw new RuntimeException("unexpected message type " + message.getClass().getName());
  }

  if (calculatedSignature != null) {
   PGPSignatureList signatureList = (PGPSignatureList) objectFactory.nextObject();
   System.out.println("signature list (" + signatureList.size() + " sigs) is " + signatureList);
   PGPSignature messageSignature = (PGPSignature) signatureList.get(0);
   System.out.println("verification signature is " + messageSignature);
   if (!calculatedSignature.verify(messageSignature)) {
    throw new RuntimeException("signature verification failed");
   }
  }

  if (encryptedData.isIntegrityProtected()) {
   if (encryptedData.verify()) {
    System.out.println("message integrity protection verification succeeded");
   } else {
    throw new RuntimeException("message failed integrity check");
   }
  } else {
   System.out.println("message not integrity protected");
  }

  //close streams
  clearDataInputStream.close();
  
  
 }

}


Cheers,
B.

CF: New version of BonCode PGP library released

I had a few request to look into the PGP library I released for ColdFusion and Railo last year. It took me a while to understand my own code, then, a while longer to implement the features that I wanted to add ;o)

The main add on this time is the ability to create single pass signed files. This allows you to create a file where you are assured that only the authorized receiver can read them, while the receiver is assured that the sender is authentic as well.  Yep, I know sound like cold-war stuff, but it is quite common scenario in financial exchanges to assure both sides that everything is the way it should be.

To a lesser level some other additions and bug fixes were completed as well.
All this, as usual is open source.

You can download code, examples, and implementation from here:

https://github.com/Bilal-S/cfpgp/releases/tag/2.0.0

Best,
B.

CF: Setting up the OWASP ESAPI Library for use with ColdFusion and Railo

If you are taking application security seriously or have been curious about it you know by now that the native tools built into ColdFusion and Railo are not sufficient to hinder the serious hacker from making headway.

To truly use best practices you can do a lot of code development, or, fall back to a project that has already proven its merit through many years of practical use.

I am referring to the OWASP Enterprise Security API (ESAPI). Unfortunately, getting this puppy running in any shape requires some reading muscle and some luck and some powers of deduction.

I am summarizing here the findings, so you don’t have to run through the maze of options and boiling it down to something simple.

First, you will have to download the jar file (as of this writing it would be esapi-2.0.1). The download is around 14MB but you only need the esapi-2.0.1.jar file. Copy the jar file to (backup any esapi file that already exists in there first):
[cfroot]/wwwroot/WEB-INF/lib in Adobe Coldfusion.
WEB-INF/lib in Railo

Then, download a good ESAPI.properties file. Most of my head banging and hair ripping surrounds finding the property definitions. Can’t stress this enough. Start with the one from source code it has good comments. Go through this file carefully and make needed changes. Make sure all directories referenced in the properties file actually exist on your drive system and also change default Encryptor.MasterKey and Encryptor.MasterSalt to something you are comfortable with, e.g. do not use something like this:

Encryptor.MasterKey=changeme
Encryptor.MasterSalt=blah

After you made changes save it (e.g. c:\esapi\files).

Thirdly, make environment start up changes.
If you are using Adobe Coldfusion you will need to change the JVM startup properties in CF Administrator to add a property and point to place where you placed ESAPI.properties file. E.g.:
-D org.owasp.esapi.resources=c:\esapi\files

For Railo, the above is done in Tomcat/Jetty startup parameters.

Fourthly, change classpath (Yep, you heard right change JAVA classpath): Add the directory you placed the properties file in to Java classpath. This is something that had me stumped as well.

After all of the above, give your server a good schake (restart), and then test whether all works.
Simple code snippet:

<cfset esapi = CreateObject("java","org.owasp.esapi.ESAPI")>

<cfset encoder = esapi.encoder()>

<cfoutput>

 <cfset myInput="<script>some input for html context; 

  alert('doing something you don't want');</script>">

  #now()# <br/>

  #encoder.encodeForHTML(JavaCast("string", myInput))#

</cfoutput>

The good news is that Adobe is looking into bundling this in the future so you don’t have to. However, in the meantime this is good practice ;o)

Best,
B.

Internet Explorer and the case of the vanishing Forms

So we ran into the problem of a customer’s users’ not being able to use our application. The users happen to use Internet Explorer and IIS (Internet Information Server) and the behavior was intermittend.
We thought, this is a network issues for sure; so we put sniffers on client and servers sides and observed that nothing was conclusive.
Another thing that threw us was that using an alternate browser such as Chrome everything seemed to be working.
When things did not work, however, we saw that IE (version 6 through 9) would not send any HTTP form information along. So even with simple HTML page that had two form fields “FirstName” and “LastName”, we would see through the protocol capture that IE started an HTTP post, but no form fields and values. They had vanished. Poof !
Our further suspicion of maybe a plugin, proxy or firewall stripping this data out was also eliminated and we started staring at each like we are all going crazy. And, of course, we googled. Nothing there either. (Google, oh Google, why did you fail us!)…
Then, a little break, we discovered, that everytime things stopped working and Forms started vanishing, the user had just entered a secured area of the site and returned to an unsecured area. The security access was transparent as IIS was setup to use “Windows Authentication” similar to “Integrated Authentication”; thus, IE was doing this in the background. Thereafter, even if the user returned to the non-secure areas of the site, IE would refuse to send Form data. After more digging we found that this seems to be intentional; we even found an old web-page that descibed this as good feature for IE6. The behavior is this: after, IE authenticates to a site via NTML / Kerberos (i.e. some integrated way), all traffic to that site has to be secured and as part of the security mechanism no plain text form submission is allowed. Great !
The solution to this was to move the secure portions to a seperate site on IIS and thus everything started working as it should. IE was happy, customer was happy, and we could go to sleep.

Hope you don’t have to spent as much time on troubleshooting knowing this.

B.

CF: Tracing AMF (Action Message Format) packages for Flex/BlazeDS in Coldfusion

I know I had seen this and done this before but for some reason I could not find it. I am looking at a Flex based component that makes remote calls to ColdFusion (Flash Remoting), then, renders some of the data. Now I wanted to find out what is happening and more specifically what data is being exchanged between Flex and ColdFusion.
As you might know, the exchange between CF and Flex is in AMF format, which is a binary format and thus not easily readable over protocol sniffer.
I know, I know, I can get many tools, and ServiceCapture is mentioned many times; but I wanted to do this simpler.
What I done in the past is used the command window to get this, but with many things, you forget, or just get older ;o)
So you can start ColdFusion in a Command / Terminal window, by going to the installation folder and finding the right startup script.
For windows:
[cfroot]\bin\cfstart.bat

This will start the ColdFusion server in command window:

Command Window running ColdFusion

However, this did not automatically decode the AMF messages or gave me insight into flash remoting. In order to that I had to find the flex services-config file. On stand-alone server install on Windows this would be located here:
[cfroot]\wwwroot\WEB-INF\flex\services-config.xml

find the logging section and change logging level to “Debug” like so:

<logging>

        <target class="flex.messaging.log.ConsoleTarget" level="Debug">

        ...

</logging>



You can even change the prefix of the messages, e.g. to Flex like so:



<logging>

        <target class="flex.messaging.log.ConsoleTarget" level="Debug">

            <properties>

                <prefix>[Flex] </prefix>

This is the cheap way of getting debugging going on the protocol and see what is being exchanged.
Hope this helps,

-B

CF: CFCamp 2011 presentation and sample code

A long day at CFCamp came to a social conclusion at the Marriott-bar. This again confirms the impression that the ColdFusion community is approachable by old and new hands alike.

Another interesting fact about the CF community emerged. According to the custodial support staff, the CFCamp attendees consumed three times as much coffee as crowds of a similar size. Definitively a mark of distinction.

Thanks to the organizers, presenters, and attendees for making this a solid CF event.
Overall, learned new things from all and had a Pretzel to boot.
As discussed, I posted the the presentation slides and sample code for download.

Cheers,
B.

CF: Munich in the Fall, CFCamp 2011

So, you just missed Oktoberfest and were wondering what else there is to do in Munich in the Fall. Well, you happen to be in luck, especially if you are a ColdFusion enthusiast.
It so happens that this year a few fellow believers in the art of the Pretzel and motivated ColdFusion learners are assembling on October 28th for CFCamp 2011.

Even yours truly will make the trek down  to Bavaria’s Capital to chat and learn from others. Will also do a talk on application security, sharing some nuggets of the school of hard knocks etc.

As far as I understand it, it is not too late to signup and the Beer and Pretzels are beckoning….

Cheers,
B