Flex Embedded In PDF - Guest Starring Coldfusion And Flash Security Issues

One of the things that I think is really cool is that there is a full flash player in Acrobat 9. It is definitely a feature that looks useful as it allows you to embed some really powerful functionality into your documents and provides a totally different model for distributing a RIA.

Luckily, it has some potential importance to my day job, so when no one was looking, I scored a few moments to test it out. It was a really good exercise as it also give me a reason to actually think about the services.config.xml.

So, I started with a really simple Hello World style application. Of course, mine was more reminescent of two teenagers talking:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:site="*">
<mx:Script>
<![CDATA[

import mx.controls.Alert;

private function hey():void {
Alert.show("hey");
}

]]>

</mx:Script>

<mx:Button label="Hey" click="hey()"/>

</mx:Application>

So I did a release build of this guy and produced a handy swf. Then, in Acrobat Pro, I opened the file and got the Insert Flash window.

The Advanced Options is definitely worth perusing, if only just to get the Flex to be enabled with the opening of the document. So you click OK and get your application sitting in Acrobat.

So click the "Hey" button and you get the expected Alert window. Nothing really magic so far. However, after I did this simple little guy, I started looking around for something more interesting to open up. That's when I had an odd realization; every app that I have created, at least anything that does anything, has based on a ColdFusion backend. While this is potentially good news for Adobe, it suddenly hit me that I have a problem. My apps really depend on having the server nice and handy in order to get the info I need to do anything even remotely interesting. In Acrobat, my application will be denied that intrinisic context and will be lost.

So it was time to take a look at the services.config.xml that I have reference in my compiler arguments. For reference, my compiler arguments look like this:

-services "C:\ColdFusion8\wwwroot\WEB-INF\flex\services-config.xml" -locale en_US -context-root ""
In this case, there is a legacy reason from my work environment for this folder path, but all I keep there now are support files from a Coldfusion installation that I need for my Flex apps.

So, since this xml document is my gateway to communications with the Coldfusion server, I opened it up. Luckily, the tag names make sense and, under channels, I found <!-- CF Based Endpoints --> followed by a bunch of channel-definition tags:

<!-- CF Based Endpoints -->
<channel-definition id="my-cfamf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://{server.name}:{server.port}{context.root}/flex2gateway/" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>

<channel-definition id="cf-polling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://{server.name}:{server.port}{context.root}/flex2gateway/cfamfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>8</polling-interval-seconds>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>

<channel-definition id="my-cfamf-secure" class="mx.messaging.channels.SecureAMFChannel">
<endpoint uri="https://{server.name}:{server.port}{context.root}/flex2gateway/cfamfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>

I'm not the quickest guy in the world, but I did figure out that {server.name} and similar items were substitution markers of some kind (haven't looked to see if they are variables or constants). So, intrepidly moving along, I did a quick subsitution of {server.name} with the IP address of my testing server, and I went ahead and just dumped {server.port} since I wasn't defining it anywhere, but kept {context.root} since I was passing that information in my compilar arguments.

So my version looks like:

<!-- CF Based Endpoints -->
<channel-definition id="my-cfamf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://192.168.1.100{context.root}/flex2gateway/" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>

<channel-definition id="cf-polling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://192.168.1.100{context.root}/flex2gateway/cfamfpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>8</polling-interval-seconds>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>

<channel-definition id="my-cfamf-secure" class="mx.messaging.channels.SecureAMFChannel">
<endpoint uri="https://192.168.1.100{context.root}/flex2gateway/cfamfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
<serialization>
<instantiate-types>false</instantiate-types>
</serialization>
</properties>
</channel-definition>
I potentially could get burned by the mods to "my-cfamf-secure", the lack of protocol, but I'll cross that bridge if I come to it.

Ok, this is good. So now we need an app that takes advantage of it. So I poached a sliver of code from an app I had; you'll need to create your own backend for testing.


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">

<mx:RemoteObject id="myRO" destination="ColdFusion" source="components.imAPI" showBusyCursor="true">
<mx:method name="getCategories" result="doGetCategories(event)" fault="Alert.show(event.fault.message);"/>
</mx:RemoteObject>

<mx:Script>
<![CDATA[

import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.utils.ObjectUtil;

public function clicker():void {
myRO.getCategories();
}

private function doGetCategories(e:ResultEvent):void {
Alert.show(mx.utils.ObjectUtil.toString(e.result));
}

]]>

</mx:Script>

<mx:Button label="hey" click="clicker()" />

</mx:Application>

Give yourself a gold star if you know why this failed. Mind you, I knew why it would fail going into it (just finished Chapter 19 of Essential Actionscript 3.0 so the security facets of the flash player were fresh in my mind - though admittedly not fully groked yet), but I wanted to see the alert pop up to make sure.

When the Flex app calls the remote object from Acrobat, it is a stranger and Flash doesn't like strangers by default. Now, here's the thing: you have the ability to specify what domains will have access, and that may work in many instances, but if you are distributing a pdf containing your app, and you want it to work for anyone who may use it, you are going to have to make your server a bit of a slut. That said you can be a bit choosy.

If you are lazy and don't feel like making any modifications to your code, you can simply create a file named crossdomain.xml and enter the following:


<?xml version="1.0">
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
For more details, check out Policy file changes in Flash Player 9 and Flash Player 10.

Anyway, that will get it up and running for you, however, it will open your site to potential mischief. If you are willing to add an extra line to your code, you can give yourself some protection by only opening a limited directory tree.

Place your remote object, CFC file in this case, in a sub-directory somewhere under your web root. In that folder, place your crossdomain.xml. Very similar to the previous version, but in this case only this folder, and any sub-folders, will be accessible to your flash (well, accessible is a pretty plastic concept here and I don't feel like digressing into the nuances of what is and is not allowed). Now, in a script tag, but not in a function, add the following line:

Security.loadPolicyFile("http://yourdomain/your/file/path/crossdomain.xml");
This line will enable the flash player to find the crossdomain.xml document. In fact, since you are explicitely identifying the file, you can name it whatever you want.

So, I have a working example that is a pdf with embedded Flex that returns the square of the input number from a remote server.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" >
<mx:RemoteObject id="myRO" destination="ColdFusion" source="supporting.pdftest.cfcs.test" showBusyCursor="true" >
<mx:method name="square" result="doGetSquare(event)" fault="Alert.show(event.fault.message);" />
</mx:RemoteObject>
<mx:Script>
    <![CDATA[
    Security.loadPolicyFile("http://blog.shortfusion.com/supporting/pdftest/cfcs/mycrossdomain.xml");
    import mx.rpc.events.ResultEvent;
    import mx.controls.Alert;
    import mx.utils.ObjectUtil;

    public function getSquare():void {
        myRO.square(inputNumber.value);
    }

    private function doGetSquare(e:ResultEvent):void {
        Alert.show(mx.utils.ObjectUtil.toString(e.result));
    }

    ]]>

</mx:Script>
<mx:NumericStepper id="inputNumber" value="1" minimum="0" maximum="1000" left="10" top="10"/>
<mx:Button label="Get Square" click="getSquare()" left="100" top="10"/>
</mx:Application>

You can test the CFC on its own at by browsing directly to it (the default value is 2, so it will display 4): http://blog.shortfusion.com/supporting/pdftest/cfcs/test.cfc?method=square

The standalone Flex can be found here:http://blog.shortfusion.com/supporting/pdftest/flex/pdftest.html

And click the following to to view an example of a pdf with embedded flex application

Note: This was a long one to produce (fought the crossdomain.xml for awhile), and I find myself pressed to do a few other things - I'll review and edit it a few more times, but definitely let me know if any of my typos cause any confusion or problems.

03FEB2009 10:26 Update
Got to work and tested my code and found that I still have a security issue when the pdf is opened from the file system (as opposed to downloaded from the server; that works fine). Will update when I have this resolved.

08FEB2009 10:26 Update & Resolution
Took longer than I wanted to get around to investigating this problem, but I have a reason for why I hav the security problem, though I don't like it. I also have a workaround, so I guess it evens out. Check out my follow-up article on flex policy files and remote objects for details.

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.001. Contact Blog Owner