Recently I’ve been plugging away at an experimental “WebGUI_flux” branch of the awesome Perl based content management system / web application framework WebGUI that wG founder JT created for me a month or so ago. I’m happy to say that the code has progressed to the point where I’m ready to announce Flux to the world and invite anyone who is interested to have a play with the demo server, explore the API, give me feedback and if you like the look of it, contribute some plugins of your own.
Here’s the original impetus for building Flux:
As per most content management systems, WebGUI’s built-in authorization mechanism is based on User Group membership. This is perfectly adequate for a large class of websites: content managers define one or more User Groups and then set the “Who Can View” and “Who Can Edit” Security Tab options on their Wobjects to the appropriate group. However in one important aspect such sites are static in nature: the set of pages that a user can access is fixed according to what groups they belong to. Authorization can be made dynamic by manipulating group membership (manually, which isn’t really dynamic, or via custom code) or by writing custom Wobject authorization logic. Both of these approaches work, however, custom code leaves content managers out of the loop.
You can find the latest version of the design doc in SVN in pdf and odt formats. Flux is quite large in scope and hard to condense into a single post, so please refer to the design doc if you want more details, but I’ll try to give a quick summary here.
For content managers:
- Flux is a rule-based authorization layer for WebGUI content managers. What that means is that you can define Rules (using a GUI) that define the conditions that must be met for a user to be given access to an Asset. Each time a user tries to access an Asset, Flux evaluates the Rule(s) you have defined to see if the user should be granted access.
- By default Flux is disabled, meaning zero performance hit and no change to the UI.
- You can enable/disable Flux at the site-wide level (via the Flux Admin Console), and also on individual Assets (via the asset’s Security Tab).
- With Flux turned on for an Asset, the Security Tab shows a combo box containing all Flux Rules that you have defined. These combo boxes appear for each action your Asset supports (e.g. “Who Can View”, “Who Can Edit”, etc..). If you pick a Rule for an action, authorization logic for that action is delegated to that particular Flux Rule.
- Rules are powerful, flexible, and pluggable.
- You define Rules via a simple, intuitive step-by-step wizard in the Flux Admin Console.
- Rules can depend on static information, user-specific information, time-dependent information, asset-specific information, and anything else we plug into the framework.
- Rules can also depend on other Rules, meaning that you can construct an arbitrarily complex graph of connected Rules. Flux makes sure that infinite loops don’t occur, and can even dynamically generate a visualisation of your Flux Graph showing all interrelationship between Rules
For developers:
- Rules are made up of one or more Expressions
- Each Expression is comprised of an Operator and two Operands.
- The UI guides content managers through the process of picking these, displaying helpful messages and prompting for any additional information required along the way
- The following Operators are built:
- IsEqualTo, IsNotEqualTo
- LessThan, LessThenOrEqualTo, GreaterThan, GreaterThanOrEqualTo
- MatchesPartialText, DoesNotMatchPartialText
- The following Operands have been built:
- TextValue – allows the content manager to enter a simple text value
- NumericValue – allows the content manager to enter a numeric value
- TruthValue – allows the content manager to enter yes/no
- DateTime – allows the content manager to enter a date from a date-picker
- Group – allows the content manager to choose a group from the wG Group combo box. The Operand evaluates to true/false depending on whether the user being tested against belongs to that group
- UserProfileField – allows the content manager to specify the name of a User Profile field. The Operand evaluates to the value of that user profile field for the user being tested against.
- FluxRule – allows the content manager to choose another Flux Rule from a combo box. The Operand evaluates to true/false depending on the result of testing that Flux Rule against the user.
- Operands can prompt the user for an arbitrary number of extra arguments as part of the step-by-step wizard. These arguments are stored as a JSON-encoded string.
- Modifiers can be registered against Operand return types, causing the UI to prompt the content manager for extra information when an Operand is chosen during Expression building. For example, the following two Modifiers have been built and registered against the DateTime type:
- DateTimeFormat – prompts the content manager for date format patterns (as per strftime()) so that the DateTime object can be formatted as a string. Also prompts the content manager for a timezone to use (any valid timezone can be specified, or ‘user’ which indicates that the timezone of the user being tested against should be used). This is useful so that you can e.g. compare a DateTime object to a string such as “Monday”.
- DateTimeCompareToNow – prompts the content manager for a timezone and a Duration unit to use (e.g. ‘hours’, ‘days’, etc..). This allows you to construct Rules that evaluate to true a certain number of days since a particular date etc..
- By default, multiple Expressions are ANDed together. But if you like you can specify an arbitrarily complex boolean logical expression instead (using AND, OR, NOT and parenthesis).
I’ve included a contrived example, which might makes things clearer. It might also make things more confusing since you don’t have the nice UI to guide you through the process of building the Expressions, but see how you go..
Imagine you have a WebGUI site with some content that you want to show to your French users only.
- You create a Rule called “French Only” and then add an Expression to it that has:
- operand1: UserProfileField
- operand1Args: {field: homeCountry} # note that this is JSON-encoded
- operator: IsEqualTo
- operand2: TextValue
- operand2Args: {value: “France”}
- You then enable Flux on the Security Tab for that content and choose the “French Only” Rule from the drop-down list for “Who Can View”… voila!
Now say you’ve been using good old-fashioned Group membership on your site. Let’s assume you have a “Premium Members” Group. On a whim, you decide your content should be restricted to French Premium Members:
- You add a second Expression to the “French Only” Rule to require the Premium Members group:
- operand1: Group
- operand1Args: {groupId: <id of Premium Members group as chosen from group combo box>}
- operator: IsEqualTo
- operand2: TruthValue
- operand2Args: {value: 1}
A few weeks later, you decide to add an extra page that French Premium Members can see on their birthday.
- You create a new Rule called “Bon Anniversaire” and add an Expression linking to your earlier “French Only” Rule:
- operand1: FluxRule
- operand1Args: {fluxRuleId: <id of the “French Only” Flux Rule as chosen from a combo box>}
- operator: IsEqualTo
- operand2: TruthValue
- operand2Args: {value: 1}
- ..and a second Expression requiring that today be the user’s birthday (in their time zone of course):
- operand1: UserProfileField
- operand1Args: {field: birthdate}
- operand1Modifier: DateTimeCompareToNow
- operand1ModifierArgs: {units: “days”, time_zone: “user”} # Modifier args are JSON-encoded too
- operator: IsEqualTo
- operand2: NumericValue
- operand2Args: {value: 0}
Cool huh?
Hopefully that gives you a feel for the power of the framework. Be sure to check out the design docs for more details (did I mention it also supports Workflow triggers?). The test suite (in t/Flux) is pretty comprehensive (currently at >85% coverage) – the Operand-specific tests in particular should get you up to speed pretty quickly on how to drive Flux.
Test Coverage Report:
---------------------------- ------ ------ ------ ------ ------ ------ ------ File stmt bran cond sub pod time total ---------------------------- ------ ------ ------ ------ ------ ------ ------ lib/WebGUI/Flux.pm 88.1 71.9 50.0 100.0 75.0 2.4 82.3 ...WebGUI/Flux/Expression.pm 93.3 85.7 60.0 89.5 100.0 17.2 89.1 ...lux/Expression/Builder.pm 100.0 n/a n/a 100.0 n/a 0.1 100.0 lib/WebGUI/Flux/Modifier.pm 84.9 61.9 44.4 100.0 100.0 2.9 77.0 ...r/DateTimeCompareToNow.pm 100.0 100.0 n/a 100.0 0.0 0.2 92.6 ...odifier/DateTimeFormat.pm 100.0 50.0 n/a 100.0 0.0 0.3 87.5 lib/WebGUI/Flux/Operand.pm 85.9 66.7 44.4 100.0 100.0 14.7 78.9 .../Flux/Operand/DateTime.pm 100.0 n/a n/a 100.0 0.0 0.2 91.3 .../Flux/Operand/FluxRule.pm 97.1 83.3 n/a 100.0 0.0 1.3 90.0 ...GUI/Flux/Operand/Group.pm 100.0 n/a n/a 100.0 0.0 0.1 90.5 ...x/Operand/NumericValue.pm 100.0 n/a n/a 100.0 0.0 0.1 89.5 ...Flux/Operand/TextValue.pm 100.0 n/a n/a 100.0 0.0 0.6 89.5 ...lux/Operand/TruthValue.pm 100.0 n/a n/a 100.0 0.0 0.1 89.5 ...erand/UserProfileField.pm 100.0 n/a n/a 100.0 0.0 0.1 90.5 lib/WebGUI/Flux/Operator.pm 84.5 53.1 33.3 100.0 100.0 14.5 75.0 ...oesNotMatchPartialText.pm 100.0 n/a n/a 100.0 0.0 0.1 95.5 ...lux/Operator/IsEqualTo.pm 100.0 100.0 100.0 100.0 0.0 1.8 96.6 ...Operator/IsGreaterThan.pm 100.0 100.0 100.0 100.0 0.0 0.2 96.6 ...IsGreaterThanOrEqualTo.pm 100.0 100.0 100.0 100.0 0.0 0.2 96.6 ...ux/Operator/IsLessThan.pm 100.0 100.0 66.7 100.0 0.0 0.2 93.1 ...or/IsLessThanOrEqualTo.pm 100.0 100.0 100.0 100.0 0.0 0.2 96.6 .../Operator/IsNotEqualTo.pm 100.0 100.0 100.0 100.0 0.0 0.2 96.6 ...tor/MatchesPartialText.pm 100.0 n/a n/a 100.0 0.0 0.1 95.5 lib/WebGUI/Flux/Rule.pm 94.9 88.9 84.4 93.3 93.8 42.4 92.3 Total 93.1 75.7 70.2 97.8 53.2 100.0 87.4 ---------------------------- ------ ------ ------ ------ ------ ------ ------
Implementation status:
- You can do just about everything through the API (check out the test suite for more info)
- You can write your own Operands and Operators as plugins. To give you an idea of how easy it is to add your own plugins, most of the current Operands require 1-3 lines of unique code. Operators are simple too.
- The Flux Admin Console is working, although not ajaxified yet
- Per-asset Flux authorization UI options are available on some Assets, although only tested on Article and PageLayout thus far and not pretty.
- I’ve only implemented a simple CRUD interface for manipulating Rules and Expressions through the Admin console. The current interface is really just a simple slap-dash job put together so that people can play with the underlying framework without needing to write code. The Expression builder in particular is quite clunky to use – you need to pass in fully-formed JSON Operand arguments. The UI has inline documentation to help you do this. The design doc has mock-ups of what the finished UI will look like.
- I haven’t implemented Wobject-bound Rules, although I will soon – this is where Flux teams up with next generation WebGUI Wobjects such as Thingy and Survey 2.0 to do some really cool things (check the design doc).
I’ve set up a demo server where you can play with Flux. The demo sites come pre-bundled with some Rules to get you started: go the the Flux item in the Admin Console and click on Flux Graph to generate your version of the image attached to this email. As mentioned, you’ll probably find the Expression builder pretty awkward since it nice step-by-step wizard isn’t built yet.
You can also download the latest version of the code from SVN and run it yourself – be aware that visualisation of the Flux Graph is currently done using GraphViz so you will need to install it on your system using something similar to “apt-get install graphviz gsfonts” and “cpan GraphViz”.
The big caveat for getting Flux included in a future version of WebGUI is performance. Obviously checking Flux Rules is more expensive than doing a single call to $user->isInGroup(). I’ve done quite a bit of work on benchmarking, profiling and optimisation, and so far things look good but there’s still work to be done. I’ll save that discussion for a later post.

