Tuesday 20 October 2009

robotLegs meets as3-signals?

The flash event model is a bit... sucky.

Robert Penner wrote these 3 blog posts which summarise the limitations:

He's been working on an alternative known as as3-signals, which you'll find on git-hub.

Essentially signals seem to provide the following enhancements: (correct me if I'm wrong here)
  1. A type based rather than string based registration for an event.
  2. Compile time checking that you only register for relevant signals. If SomeClass doesn't have a .clicked signal then you won't be able to listen for it.
  3. Centralised and handy functions for removing all listeners from an event.
  4. The ability to add a listener for one time only (and have it automatically removed).
  5. Useful runtime errors (if you use 'addOnce' after you've already used 'add' or the reverse you get a sensible error).
  6. The option to prioritise listeners - enforcing a calling order.
I'm currently working on this modularised rebuild of an existing piece of complex software. Inter-module communication is the thing I'm thinking about most.

I've also been checking out the interesting stuff over at robotlegs - a DI driven framework which looks light and really focussed... except that Flash CS4 won't let you use the metadata required, so it seems to be Flex only for now.

However, it has a nice command and event mapping model - allowing for type checking, 'one shot only' and so on.

So... I'm considering the benefits of a hybrid. Of creating a command mapped version of as3-signals.

Questions:
1) How to replicate the nice compile time checking that Signals provides while also supporting the decoupling of the modules via the centralised command map - so you can't register for an event that will never exist.

2) Where to put the events? Which package? Do they need to reside in a centralised area?

3) Is this a complete waste of time? Does the command map already provide sufficient decoupling? Should I simply use signals within a module and the command map between modules?

4) Could a module create a new signal 'channel' dynamically, or does that make no sense at all?

Questions... questions...

Wednesday 16 September 2009

A framework for Modular AIR Applications

I've finished building a framework for securely loading signed, packaged swfs into the application sandbox at runtime, while leaving others in the non-application sandbox and rejecting modules without matched signatures.


The framework fairly neatly packages the whole process.

The origin of the signature testing process is the Adobe article here, where you can also download a compiled code swc of the necessary flex-only classes if you're working in Flash.

The air application I've built as proof-of-principle achieves the following:
  • Modules with matching signatures are loaded to the app sandbox (if requested).
  • Modules without matching signatures requesting app sandbox loading are rejected.
  • Modules can also be loaded to the non-application sandbox.

It demonstrates:
  • That app sandbox loaded modules can write to the file system (they put a directory on your desktop).
  • That non-app sandbox loaded modules are prevented from writing to the file system.
  • That both app and non-app sandbox loaded modules can pass library assets to the main application to be added to the display list.

It has a lot of user feedback / developer feedback built in. In the absence of the ability to trace in the air application itself I'm listening for this feedback and displaying it in a textarea in the main application.

Currently the information about what to load and how to load it, and where to find the .zip assets on a server to install modules, is all contained in moduleData.xml inside the applicationFlasAndCode folder. When you start the application you must browse to this file.

To use the framework:
  1. Use ModuleXMLLoader to load an xml document containing your module information.
  2. This creates a strongly typed iterator: ModuleDescriptionIterator.
  3. Instantiate ModuleChainLoader, passing it that iterator.
  4. Listen for the ModuleEvent.MODULE_LOADING_COMPLETE event.
  5. Ask the moduleChainLoader to startLoadingModules().
  6. When you handle the loading complete event, run getModuleDictionary().
  7. Access your modules from the ModuleDictionary using getModule() and getSandboxedModule(). You pass the module's unique name to those functions. They pass back the required module.

Any questions at all, post them here or email me.


How secure is secure?

I'm building an enterprise training application. It already exists in AIR, but we're moving to a modular system because our users don't have the permissions required to run the automatic updates.

Secure, for me, is secure enough not to be the preferred target for a malicious attack. Nothing is impossible, but I want to make it sufficiently difficult / tedious that anyone intending to cause trouble looks elsewhere to do it.


Notes:

In my own final application the xml will be a secure data stream coming from a server.

If you want to keep my xml structure you can use the ModuleXMLLoader class as is, but I've kept it as a separate stage in the process so that you can make changes to this.


Some possible gotchas to avoid:
  • The example module flas have classes which extend ITestableModule. You'll need to point flash to the com folder in order for this interface to be found.
  • Remember that flash keeps signing with the last certificate you used. So when creating good and bad modules, and compiling the app itself, keep an eye on which certificate you're using.
  • Module .air packages need to be renamed .zip
  • You cannot test secure loading in the flash test player. You have to create the application .air file, install it and run it to test it.
  • Don't forget to grab that SignatureUtils.swc from the Adobe link.

Tuesday 1 September 2009

Creating modular applications in Adobe AIR

Identity is on hold at the moment, work still being too hectic. I'm hoping to really get down to it next year - I've got 3 O'Reilly books on the way out by March next year, and they're eating all my time.

In the meantime, the main work project I've got going on is a nice fit with what Identity needs to achieve in terms of modularity. It's an e-learning app, already running successfully as an Air application, but we're rebuilding it to achieve the following:

1) Dynamically built modular environment. Other than the AIR shell itself, all the functionality will be loaded by the app at runtime. This allows for non-admin updates, and for user A and user B to have different logging systems / quiz engine etc.

2) Reskinned at runtime. Different departments and sub companies of the corporate client can have a different look and feel, maybe even a different screen size requirement.

I've already grappled with the Air security sandbox stuff within this app. The lessons themselves are dynamically loaded and using sandbox bridge for communication. The current quiz apps are also decoupled, and only simple data is passing around - mostly I'm using strings of xml for more structured data requirements.

I've been playing with the two variations of loading content into flash:

A) The light side: use a normal loader, put your downloaded content into a sandbox where it can only access other content via the safe and secure bridge.

B) The dark side: read the file into a ByteArray and use loadBytes() with allowLoadBytesCodeExecution = true on the loader context, to give your loaded content the run of the place... including the fileSystem.

The pros and cons of breaking the Air security model

The light side:

Events
Events must be passed using loaderInfo.sharedEvents. Only built-in events keep their type, but that's ok. The actual Event.type property is just a plain string anyway, so you can still make use of a custom event for compile time checking to avoid typos, as the Air shell doesn't care that it didn't know what MyCustomEvent.DOG_BARKED was - it just sees "dogBarked". On the down side you can't pass useful extra data with your custom events.

Security
You can use sensible functions like storeAsset(assetPath:String) to pull in things and read / write to the file system, rather than opening up your user's computer for an attack by rogue dynamically loaded modules.

Loading grandchildren
Loading a grandchild asset such as a png or jpg seems to be possible using loadBytes() with code execution set to false. You have to pass the byteArray as a normal array, reconstruct it to be a byteArray, and then load the content in the child module. I've only part tested this. More to follow. And it's pretty gross as a process.

Decoupling logic and events is ok
Modularising the logic of the application doesn't actually seem to be too hard. The code was pretty nicely decoupled already, and we think we can reduce inter-module communication to a combination of vanilla events and xml (passed as string). We'd use the Air shell as a postman to deliver messages from one module to another. There would be an unnerving amount of dynamic stuff which isn't checkable at compile time, but it's doable.

Major hurdle: providing skin assets at runtime
I simply can't find any way to load the actual graphics for the modules at runtime. All I want is to load a specific (dynamically selected) swf with a bunch of MCs in the library, and to be able to do stuff like "new LoginButtonUpSkin()" and whack the requested graphic into the right place. I guess we could export all of our graphics as pngs and use the loadBytes() trick to feed them to the individual modules, but how grim is that?

The other option would be to compile variations on the modules themselves, with different look and feel. "MainMenuGreen.swf" "MainMenuBlue.swf". That feels like a huge pain in the ass when it comes to creating new skins. The designers would need access to the code library, they always ring me up wondering how to link their files to the com folder... you can see the potential headaches I'm sure...

The dark side:

Events: Module to module event traffic is possible. Events don't lose their typing when passing from module to module.

Security: This is dangerous stuff. But all my modules are coming from a trusted server and there's a technique for verifying signatures. Basically any module that wants to be loaded into the application sandbox using the loadBytes() method would have to be an Air app in itself. Then that air package gets loaded and ripped open, the signature verified and the module loaded or rejected. I've no doubt that it's not failsafe.

Will adobe continue to provide this work-around?
The adobe official documentation on working securely with untrusted content end with the following fear-inducing statement:

Note: In a future release of Adobe AIR, this API may change. When that occurs, you may need to recompile content that uses theallowLoadBytesCodeExecution property of the LoaderContext class.

A future release WHEN? What? And will I need to recompile content even if it's happily running. Will Air 2.0 break my 1.5 application if I've used this? Presumably Adobe would provide a better work around for achieving this, but my client doesn't want to do a thousand admin-requiring reinstalls... which is why we're going to use a module based system in the first place...

At the moment I'm still testing, still lurching from favouring one side to the other. More to follow as it unfolds. Hopefully I can save someone the three days of googling and testing I've just been through.