summaryrefslogtreecommitdiffstats
path: root/doc/mapping.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/mapping.rst')
-rw-r--r--doc/mapping.rst1609
1 files changed, 1609 insertions, 0 deletions
diff --git a/doc/mapping.rst b/doc/mapping.rst
new file mode 100644
index 0000000..3363550
--- /dev/null
+++ b/doc/mapping.rst
@@ -0,0 +1,1609 @@
+Operation Model
+===============
+
+The assertions from an IdP are stored in an associative array. A
+sequence of rules are applied, the first rule which returns success is
+considered a match. During the execution of each rule values from the
+assertion can be tested and transformed with the results selectively
+stored in variables local to the rule. If the rule succeeds an
+associative array of mapped values is returned. The mapped values are
+taken from the local variables set during the rule execution. The
+definition of the rules and mapped results are expressed in JSON
+notation.
+
+A rule is somewhat akin to a function in a programming language. It
+starts execution with a set of predefined local variables. It executes
+statements which are grouped together in blocks. Execution continues
+until an `exit`_ statement returning a success/fail result is
+executed or until the last statement is reached which implies
+success. The remaining statements in a block may be skipped via a
+`continue`_ statement which tests a condition, this is equivalent to
+an "if" control flow of logic in a programming language.
+
+Rule execution continues until a rule returns success. Each rule has a
+`mapping`_ associative array bound to it which is a template for the
+transformed result. Upon success the `mapping`_ template for the
+rule is loaded and the local variables from the successful rule are
+used to populate the values in the `mapping`_ template yielding the
+final mapped result.
+
+If no rules returns success authentication fails.
+
+
+Pseudo Code Illustrating Operational Model
+------------------------------------------
+
+::
+
+ mapped = null
+ foreach rule in rules {
+ result = null
+ initialize rule.variables with pre-defined values
+
+ foreach block in rule.statement_blocks {
+ for statement in block.statements {
+ if statement.verb is exit {
+ result = exit.status
+ break
+ }
+ elif statement.verb is continue {
+ break
+ }
+ }
+ if result {
+ break
+ }
+ if result == null {
+ result = success
+ }
+ if result == success {
+ mapped = rule.mapping(rule.variables)
+ }
+ return mapped
+
+
+
+Structure Of Rule Definitions
+=============================
+
+Rules are loaded by the rule processor via a JSON document called a
+rule definition. A definition has an *optional* set of mapping
+templates and a list of rules. Each rule has specifies a mapping
+template and has a list of statement blocks. Each statement block has
+a list of statements.
+
+In pseudo-JSON (JSON does not have comments, the ... ellipsis is a
+place holder):
+
+::
+
+ {
+ "mappings": {
+ "template1": "{...}",
+ "template2": "{...}"
+ },
+ "rules": [
+ { # Rule 0. A rule has a mapping or a mapping name
+ # and a list of statement blocks
+
+ "mapping": {...},
+ # -OR-
+ "mapping_name": "template1",
+
+ "statement_blocks": [
+ [ # Block 0
+ [statement 0]
+ [statement 1]
+ ],
+ [ # Block 1
+ [statement 0]
+ [statement 1]
+ ],
+
+ ]
+ },
+ { # Rule 1 ...
+ }
+ ]
+
+ }
+
+Mapping
+-------
+
+A mapping template is used to produce the final associative array of
+name/value pairs. The template is a JSON Object. The value in a
+name/value pair can be a constant or a variable. If the template value
+is a variable the value of the variable is retrieved from the set of
+local variables bound to the rule thereby replacing it in the final
+result.
+
+For example given this mapping template and rule variables in JSON:
+
+template:
+
+::
+
+ {
+ "organization": "BigCorp.com",
+ "user: "$subject",
+ "roles": "$roles"
+ }
+
+local variables:
+
+::
+
+ {
+ "subject": "Sally",
+ "roles": ["user", "admin"]
+ }
+
+The final mapped results would be:
+
+::
+
+ {
+ "organization": "BigCorp.com",
+ "user: "Sally",
+ "roles": ["user", "admin"]
+ }
+
+
+Each rule must bind a mapping template to the rule. The mapping
+template may either be defined directly in the rule via the
+``mapping`` key or referenced by name via the ``mapping_name`` key.
+
+If the ``mapping_name`` is specified the mapping is looked up in a
+table of mapping templates bound to the Rule Processor. Using the name
+of a mapping template is useful when many rules generate the exact
+same template values.
+
+If both ``mapping`` and ``mapping_name`` are defined the locally bound
+``mapping`` takes precedence.
+
+Syntax
+------
+
+The logic for a rule consists of a sequence of statements grouped in
+blocks. A statement is similar to a function call in a programming
+language.
+
+A statement is a list of values the first of which is a verb which
+defines the operation the statement will perform. Think of the
+`verbs`_ as function names or operators. Following the verb are
+parameters which may be constants or variables. If the statement
+assigns a value to a variable left hand side of the assignment (lhs)
+is always the first parameter following the verb in the list of
+statement values.
+
+For example this statement in JSON:
+
+::
+
+ ["split", "$groups", "$assertion[Groups]", ":"]
+
+will assign an array to the variable ``$groups``. It looks up the
+string named ``Groups`` in the assertion which is a colon (:)
+separated list of group names splitting that string on the colon
+character.
+
+Statements **must** be grouped together in blocks. Therefore a rule is
+a sequence of blocks and block is a sequence of statements. The
+purpose of blocks is allow for crude flow of control logic. For
+example this JSON rule has 4 blocks.
+
+::
+
+ [
+ [
+ ["set", $user, ""],
+ ["set", $roles, []]
+ ],
+ [
+ ["in", "UserName", "$assertion"],
+ ["continue", "if_not_success"],
+ ["set", "$user", "$assertion[UserName"],
+ ],
+ [
+ ["in", "subject", "$assertion"],
+ ["continue", "if_not_success"],
+ ["set", "$user", "$assertion[subject]"],
+ ],
+ [
+ ["length", "$temp", "$user"],
+ ["compare", "$temp", ">", 0],
+ ["exit", "rule_fails", "if_not_success"]
+ ["append" "$roles", "unprivileged"]
+ ]
+ ]
+
+The rule will succeed if either ``UserName`` or ``subject`` is defined
+in the assertion and if so the local variable ``$user`` will be set to
+the value found in the assertion and the "unprivileged" role will be
+appended to the roles array.
+
+The first block performs initialization. The second block tests to see
+if the assertion has the key ``UserName`` if not execution continues
+at the next block otherwise the value of UserName in the assertion is
+copied into the variable ``$user``. The third block performs a similar
+operation looking for a ``subject`` in the assertion. The fourth block
+checks to see if the ``$user`` variable is empty, if it is empty the
+rule fails because it didn't find either a ``UserName`` nor a
+``subject`` in the assertion. If ``$user`` is not empty the
+"unprivileged" role is appended and the rule succeeds.
+
+Data Types
+----------
+
+There are 7 supported types which equate to the types available in
+JSON. At the time of this writing there are 2 implementations of this
+Mapping specification, one in Python and one in Java. This table
+illustrates how each data type is represented. The first two columns
+are definitions from an abstract specification. The JSON column
+enumerates the data type JSON supports. The Mapping column lists the
+7 enumeration names used by the Mapping implemenation in each
+language. The following columns list the concrete data type used in
+that language.
+
++-----------+------------+--------------------+---------------------+
+| JSON | Mapping | Python | Java |
++===========+============+====================+=====================+
+| object | MAP | dict | Map<String, Object> |
++-----------+------------+--------------------+---------------------+
+| array | ARRAY | list | List<Object> |
++-----------+------------+--------------------+---------------------+
+| string | STRING | unicode (Python 2) | String |
+| | +--------------------+ |
+| | | str (Python 3) | |
++-----------+------------+--------------------+---------------------+
+| | INTEGER | int | Long |
+| number +------------+--------------------+---------------------+
+| | REAL | float | Double |
++-----------+------------+--------------------+---------------------+
+| true | | | |
++-----------+ BOOLEAN | bool | Boolean |
+| false | | | |
++-----------+------------+--------------------+---------------------+
+| null | NULL | None | null |
++-----------+------------+--------------------+---------------------+
+
+
+Rule Debugging and Documentation
+--------------------------------
+
+If the rule processor reports an error or if you're debugging your
+rules by enabling DEBUG log tracing then you must be able to correlate
+the reported statement to where it appears in your rule JSON source. A
+message will always identify a statement by the rule number, block
+number within that rule and the statement number within that
+block. However once your rules become moderately complex it will
+become increasingly difficult to identify a statement by counting
+rules, blocks and statements.
+
+A better approach is to tag rules and blocks with a name or other
+identifying string. You can set the `Reserved Variables`_
+``rule_name`` and ``block_name`` to a string of your choice. These
+strings will be reported in all messages along with the rule, block
+and statement numbers.
+
+JSON does not permit comments, as such you cannot include explanatory
+comments next to your rules, blocks and statements in the JSON
+source. The ``rule_name`` and ``block_name`` can serve a similar
+purpose. By putting assignments to these variables as the first
+statement in a block you'll both document your rules and be able to
+identify specific statements in log messages.
+
+During rule execution the ``rule_name`` and ``block_name`` are
+initialized to the empty string at the beginning of each rule and
+block respectively.
+
+The above example is augmented to include this information. The rule
+name is set in the first statement in the first block.
+
+::
+
+ [
+ [
+ ["set", "$rule_name", "Must have UserName or subject"],
+ ["set", "block_name", "Initialization"],
+ ["set", $user, ""],
+ ["set", $roles, []]
+ ],
+ [
+ ["set", "block_name", "Test for UserName, set $user"],
+ ["in", "UserName", "$assertion"],
+ ["continue", "if_not_success"],
+ ["set", "$user", "$assertion[UserName"],
+ ],
+ [
+ ["set", "block_name", "Test for subject, set $user"],
+ ["in", "subject", "$assertion"],
+ ["continue", "if_not_success"],
+ ["set", "$user", "$assertion[subject]"],
+ ],
+ [
+ ["set", "block_name", "If not $user fail, else append unprivileged to roles"],
+ ["length", "$temp", "$user"],
+ ["compare", "$temp", ">", 0],
+ ["exit", "rule_fails", "if_not_success"]
+ ["append" "$roles", "unprivileged"]
+ ]
+ ]
+
+
+
+
+Variables
+---------
+
+
+Variables always begin with a dollar sign ($) and are followed by an
+identifier which is any alpha character followed by zero or more
+alphanumeric or underscore characters. The variable may optionally be
+delimited with braces ({}) to separate the variable from surrounding
+text. Three types of variables are supported:
+
+* scalar
+* array (indexed by zero based integer)
+* associative array (indexed by string)
+
+Both arrays and associative arrays use square brackets ([]) to specify
+a member of the array. Examples of variable usage:
+
+::
+
+ $name
+ ${name}
+ $groups[0]
+ ${groups[0]}
+ $properties[key]
+ ${properties[key]}
+
+An array or an associative array may be referenced by it's base name
+(omitting the indexing brackets). For example the associative array
+array named "properties" is referenced using it's base name
+``$properties`` but if you want to access a member of the "properties"
+associative array named "duration" you would do this ``$properties[duration]``
+
+This is not a general purpose language with full expression
+syntax. Only one level of variable lookup is supported. Therefore
+compound references like this
+
+::
+
+ $properties[$groups[2]]
+
+will not work.
+
+
+Escaping
+^^^^^^^^
+
+If you need to include a dollar sign in a string (where it is
+immediately followed by either an identifier or a brace and identifier)
+and do not want to have it be interpreted as representing a variable
+you must escape the dollar sign with a backslash, for example
+"$amount" is interpreted as the variable ``amount`` but "\\$amount"
+is interpreted as the string "$amount" .
+
+
+Reserved Variables
+------------------
+
+A rule has the following reserved variables:
+
+assertion
+ The current assertion values from the federated IdP. It is a
+ dictionary of key/value pairs.
+
+regexp_array
+ The regular expression groups from the last successful regexp match
+ indexed by number. Group 0 is the entire match. Groups 1..n are
+ the corresponding parenthesized group counting from the left. For
+ example regexp_array[1] is the first group.
+
+regexp_map
+ The regular expression groups from the last successful regexp match
+ indexed by group name.
+
+rule_number
+ The zero based index of the currently executing rule.
+
+rule_name
+ The name of the currently executing rule. If the rule name has not
+ been set it will be the empty string.
+
+block_number
+ The zero based index of the currently executing block within the
+ currently executing rule.
+
+block_name
+ The name of the currently executing block. If the block name has not
+ been set it will be the empty string.
+
+
+statement_number
+ The zero based index of the currently executing statement within the
+ currently executing block.
+
+
+Examples
+========
+
+Split a fully qualified username into user and realm components
+---------------------------------------------------------------
+
+It's common for some IdP's to return a fully qualified username
+(e.g. principal or subject). The fully qualified username is the
+concatenation of the user name, separator and realm name. A common
+separator is the @ character. In this example lets say the fully
+qualified username is ``bob@example.com`` and you want to return the
+user and realm as independent values in your mapped result. The
+username appears in the assertion as the value ``Principal``.
+
+Our strategy will be to use a regular expression identify the user and
+realm components and then assign them to local variables which will
+then populate the mapped result.
+
+The mapping in JSON is:
+
+::
+
+ {
+ "user": "$username",
+ "realm": "$domain"
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "Principal": "bob@example.com"
+ }
+
+Our rule is:
+
+::
+
+ [
+ [
+ ["in", "Principal", "assertion"],
+ ["exit", "rule_fails", "if_not_success"],
+ ["regexp", "$assertion[Principal]", (?P<username>\\w+)@(?P<domain>.+)"],
+ ["set", "$username", "$regexp_map[username]"],
+ ["set", "$domain", "$regexp_map[domain]"],
+ ["exit, "rule_succeeds", "always"]
+ ]
+ ]
+
+Rule explanation:
+
+Block 0:
+
+0. Test if the assertion contains a Principal value.
+1. Abort the rule if the assertion does not contain a Principal
+ value.
+2. Apply a regular expression the the Principal value. Use named
+ groupings for the username and domain components for clarity.
+3. Assign the regexp group username to the $username local variable.
+4. Assign the regexp group domain to the $domain local variable.
+5. Exit the rule, apply the mapping, return the mapped values. Note, an
+ explicit `exit`_ is not required if there are no further statements
+ in the rule, as is the case here.
+
+The mapped result in JSON is:
+
+::
+
+ {
+ "user": "bob",
+ "realm": "example.com"
+ }
+
+Build a set of roles based on group membership
+----------------------------------------------
+
+Often one wants to grant roles to a user based on their membership in
+certain groups. In this example let's say the assertion contains a
+``Groups`` value which is a colon separated list of group names. Our
+strategy is to split the ``Groups`` assertion value into an array of
+group names. Then we'll test if a specific group is in the groups
+array, if it is we'll add a role. Finally if no roles have been mapped
+we fail. Users in the group "student" will get the role "unprivileged"
+and users in the group "helpdesk" will get the role "admin".
+
+The mapping in JSON is:
+
+::
+
+ {
+ "roles": "$roles",
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "Groups": "student:helpdesk"
+ }
+
+Our rule is:
+
+::
+
+ [
+ [
+ ["in", "Groups", "assertion"],
+ ["exit", "rule_fails", "if_not_success"],
+ ["set", "$roles", []],
+ ["split", "$groups", "$assertion[Groups]", ":"],
+ ],
+ [
+ ["in", "student", "$groups"],
+ ["continue", "if_not_success"],
+ ["append", "$roles", "unprivileged"]
+ ],
+ [
+ ["in", "helpdesk", "$groups"],
+ ["continue", "if_not_success"],
+ ["append", "$roles", "admin"]
+ ],
+ [
+ ["unique", "$roles", "$roles"],
+ ["length", "$temp", "roles"],
+ ["compare", $temp", ">", 0],
+ ["exit", "rule_fails", "if_not_success"]
+ ]
+
+ ]
+
+Rule explanation:
+
+Block 0
+
+0. Test if the assertion contains a Groups value.
+1. Abort the rule if the assertion does not contain a Groups
+ value.
+2. Initialize the $roles variable to an empty array.
+3. Split the colon separated list of group names into an array of
+ individual group names
+
+Block 1
+
+0. Test if "student" is in the $groups array
+1. Exit the block if it's not.
+2. Append "unprivileged" to the $roles array
+
+Block 2
+
+0. Test if "helpdesk" is in the $groups array
+1. Exit the block if it's not.
+2. Append "admin" to the $roles array
+
+Block 3
+
+0. Strip any duplicate roles that might have been appended to the
+ $roles array to assure each role is unique.
+1. Count how many members are in the $roles array, assign the
+ length to the $temp variable.
+2. Test to see if the $roles array had any members.
+3. Fail if no roles had been assigned.
+
+The mapped result in JSON is:
+
+::
+
+ {
+ "roles": ["unprivileged", "admin"]
+ }
+
+However, suppose whatever is receiving your mapped results is not
+expecting an array of roles. Instead it expects a comma separated list
+in a string. To accomplish this add the following statement as the
+last one in the final block:
+
+::
+
+ ["join", "$roles", "$roles", ","]
+
+Then the mapped result will be:
+
+::
+
+ {
+ "roles": "unprivileged,admin"]
+ }
+
+
+
+
+White list certain users and grant them specific roles
+------------------------------------------------------
+
+Suppose you have certain users you always want to unconditionally
+accept and authorize with specific roles. For example if the user is
+"head_of_IT" then assign her the "user" and "admin" roles. Otherwise
+keep processing. The list of white listed users is hard-coded into the
+rule.
+
+The mapping in JSON is:
+
+::
+
+ {
+ "user": $user,
+ "roles": "$roles",
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "UserName": "head_of_IT"
+ }
+
+Our rule in JSON is:
+
+::
+
+ [
+ [
+ ["in", "UserName", "assertion"],
+ ["exit", "rule_fails", "if_not_success"],
+ ["in", "$assertion[UserName]", ["head_of_IT", "head_of_Engineering"]],
+ ["continue", "if_not_success"],
+ ["set", "$user", "$assertion[UserName"]
+ ["set", "$roles", ["user", "admin"]],
+ ["exit", "rule_succeeds", "always"]
+ ],
+ [
+ ...
+ ]
+ ]
+
+Rule explanation:
+
+Block 0
+
+0. Test if the assertion contains a UserName value.
+1. Abort the rule if the assertion does not contain a UserName
+ value.
+2. Test if the user is in the hardcoded list of white listed users.
+3. If the user isn't in the white listed array then exit the block and
+ continue execution at the next block.
+4. Set the $user local variable to $assertion[UserName]
+5. Set the $roles local variable to the hardcoded array containing
+ "user" and "admin"
+6. We're done, unconditionally exit and return the mapped result.
+
+Block 1
+
+0. Further processing
+
+The mapped result in JSON is:
+
+::
+
+ {
+ "user": "head_of_IT",
+ "roles": ["users", "admin"]
+ }
+
+
+Black list certain users
+------------------------
+
+Suppose you have certain users you always want to unconditionally
+deny access to by placing them in a black list. In this example the
+user "BlackHat" will try to gain access. The black list includes the
+users "BlackHat" and "Spook".
+
+The mapping in JSON is:
+
+::
+
+ {
+ "user": $user,
+ "roles": "$roles",
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "UserName": "BlackHat"
+ }
+
+Our rule in JSON is:
+
+::
+
+ [
+ [
+ ["in", "UserName", "assertion"],
+ ["exit", "rule_fails", "if_not_success"],
+ ["in", "$assertion[UserName]", ["BlackHat", "Spook"]],
+ ["exit", "rule_fails", "if_success"]
+ ],
+ [
+ ...
+ ]
+ ]
+
+Rule explanation:
+
+Block 0
+
+0. Test if the assertion contains a UserName value.
+1. Abort the rule if the assertion does not contain a UserName
+ value.
+2. Test if the user is in the hard-coded list of black listed users.
+3. If the test succeeds then immediately abort and return failure.
+
+Block 1
+
+0. Further processing
+
+The mapped result in JSON is:
+
+::
+
+ Null
+
+Format Strings and/or Concatenate Strings
+-----------------------------------------
+
+You can replace variables in a format string using the `interpolate`_
+verb. String concatenation is trivially placing two variables adjacent
+to one another in a format string. Suppose you want to form an email
+address from the username and domain in an assertion.
+
+The mapping in JSON is:
+
+::
+
+ {
+ "email": $email,
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "UserName": "Bob",
+ "Domain": "example.com"
+ }
+
+Our rule in JSON is:
+
+::
+
+ [
+ [
+ ["interpolate", "$email", "$assertion[UserName]@$assertion[Domain]"],
+ ]
+ ]
+
+Rule explanation:
+
+Block 0
+
+0. Replace the variable $assertion[UserName] with it's value and
+ replace the variable $assertion[Domain] with it's value.
+
+The mapped result in JSON is:
+
+::
+
+ {
+ "email": "Bob@example.com",
+ }
+
+
+Note, sometimes it's necessary to utilize braces to separate variables
+from surrounding text by using the brace notation. This can also make
+the format string more readable. Using braces to delimit variables the
+above would be:
+
+::
+
+ [
+ [
+ ["interpolate", "$email", "${assertion[UserName]}@${assertion[Domain]}"],
+ ]
+ ]
+
+
+
+Make associative array lookups case insensitive
+-----------------------------------------------
+
+Many systems treat field names as case insensitive. By default
+associative array indexing is case sensitive. The solution is to lower
+case all the keys in an associative array and then only use lower case
+indices. Suppose you want the assertion associative array to be case
+insensitive.
+
+The mapping in JSON is:
+
+::
+
+ {
+ "user": $user,
+ }
+
+The assertion in JSON is:
+
+::
+
+ {
+ "UserName": "Bob"
+ }
+
+Our rule in JSON is:
+
+::
+
+ [
+ [
+ ["lower", "$assertion", "$assertion"],
+ ["in", "username", "assertion"],
+ ["exit", "rule_fails", "if_not_success"],
+ ["set", "$user", "$assertion[username"]
+ ]
+ ]
+
+Rule explanation:
+
+Block 0
+
+0. Lower case all the keys in the assertion associative array.
+1. Test if the assertion contains a username value.
+2. Abort the rule if the assertion does not contain a username
+ value.
+3. Assign the username value in the assertion to $user
+
+The mapped result in JSON is:
+
+::
+
+ {
+ "user": "Bob",
+ }
+
+
+Verbs
+=====
+
+The following verbs are supported:
+
+* `set`_
+* `length`_
+* `interpolate`_
+* `append`_
+* `unique`_
+* `regexp`_
+* `regexp_replace`_
+* `split`_
+* `join`_
+* `lower`_
+* `upper`_
+* `compare`_
+* `in`_
+* `not_in`_
+* `exit`_
+* `continue`_
+
+Some verbs have a side effects. A verb may set a boolean success/fail
+result which may then be tested with a subsequent verb. For example
+the ``fail`` verb can be used to indicate the rule fails if a prior
+result is either ``success`` or ``not_success``. The ``regexp`` verb
+which performs a regular expression search on a string stores the
+regular expression sub-matches as a side effect in the variables
+``$regexp_array`` and ``$regexp_map``.
+
+
+Verb Definitions
+================
+
+set
+---
+
+``set $variable value``
+
+$variable
+ The variable being assigned (i.e. lhs)
+
+value
+ The value to assign to the variable (i.e. rhs). The value may be
+ another variable or a constant.
+
+**set** assigns a value to a variable, in other words it's an
+assignment statement.
+
+Examples:
+^^^^^^^^^
+
+Initialize a variable to an empty array.
+
+::
+
+ ["set", "$groups", []]
+
+Initialize a variable to an empty associative array.
+
+::
+
+ ["set", "$groups", {}]
+
+Assign a string.
+
+::
+
+ ["set", "$version", "1.2.3"]
+
+Copy the ``UserName`` value from the assertion to a temporary variable.
+
+::
+
+ ["set", "$temp", "$assertion[UserName]"],
+
+
+Get the 2nd item in an array (array indexing is zero based)
+
+::
+
+ ["set", "$group", "$groups[1]"]
+
+
+Set the associative array entry "IdP" to "kdc.example.com".
+
+::
+
+ ["set", "$metadata[IdP]", "kdc.example.com""]
+
+--------------------------------------------------------------------------------
+
+length
+------
+
+``length $variable value``
+
+$variable
+ The variable which receives the length value
+
+value
+ The value whose length is to be determined. May be one of array,
+ associative array, or string.
+
+**length** computes the number of items in the value. How this is done
+depends upon the type of value:
+
+array
+ The length is the number of items in the array.
+
+associative array
+ The length is the number of key/value pairs in the associative
+ array.
+
+string
+ The length is the number of *characters* (not octets) in the
+ string.
+
+Examples:
+^^^^^^^^^
+
+Count how many items are in the ``$groups`` array and assign that
+value to the ``$groups_length`` variable.
+
+::
+
+ ["length", "$groups_length", "$groups"]
+
+Count how many key/value pairs are in the ``$assertion`` associative
+array and assign that value to the ``$num_assertion_values`` variable.
+
+::
+
+ ["length", "$num_assertion_values", "$assertion"]
+
+Count how many characters are in the assertion's UserName and assign
+the value to ``$username_length``.
+
+::
+
+ ["length", "$user_name_length", "$assertion[UserName]"]
+
+
+--------------------------------------------------------------------------------
+
+interpolate
+-----------
+
+``interpolate $variable string``
+
+$variable
+ This variable is assigned the result of the interpolation.
+
+string
+ A string containing references to variables which will be replaced
+ in the string.
+
+**interpolate** replaces each occurrence of a variable in a string with
+it's value. The result is assigned to $variable.
+
+Examples:
+^^^^^^^^^
+
+Form an email address given the username and domain. If the username
+is "jane" and the domain is "example.com" then $email will be
+"jane@example.com"
+
+::
+
+ ["interpolate", "$email", "${username}@${domain}"]
+
+
+--------------------------------------------------------------------------------
+
+
+append
+------
+
+``append $variable value``
+
+$variable
+ This variable **must** be an array. It is modified in place by
+ appending ``value`` to the end of the array.
+
+value
+ The value to append to the end of the array.
+
+**append** adds a value to end of an array.
+
+Examples:
+^^^^^^^^^
+
+Append the role "qa_test" to the roles list.
+
+::
+
+ ["append", "$roles", "qa_test"]
+
+
+--------------------------------------------------------------------------------
+
+
+unique
+------
+
+``unique $variable value``
+
+$variable
+ This variable is assigned the unique values in the ``value``
+ array.
+
+value
+ An array of values. **must** be an array.
+
+**unique** builds an array of unique values in ``value`` by stripping
+out duplicates and assigns the array of unique values to
+``$variable``. The order of items in the ``value`` array are
+preserved.
+
+Examples:
+^^^^^^^^^
+
+$one_of_a_kind will be assigned ["a", "b"]
+
+::
+
+ ["unique", "$one_of_a_kind", ["a", "b", "a"]]
+
+
+--------------------------------------------------------------------------------
+
+regexp
+------
+
+``regexp string pattern``
+
+string
+ The string the regular expression pattern is applied to.
+
+pattern
+ The regular expression pattern.
+
+**regexp** performs a regular expression match against ``string``. The
+regular expression pattern syntax is defined by the regular expression
+implementation of the language this API is written in.
+
+Pattern groups are a convenient way to select sub-matches. Pattern
+groups may accessed by either group number or group name. After a
+successful regular expression match the groups are stored in the
+special variables ``$regexp_array`` and
+``$regexp_map``.
+
+``$regexp_array`` is used to access the groups by
+numerical index. Groups are numbered by counting the left parenthesis
+group delimiter starting at 1. Group 0 is the entire
+match. ``$regexp_array`` is valid irregardless of whether you used
+named groups or not.
+
+``$regexp_map`` is used to access the groups by
+name. ``$regexp_map`` is only valid if you used named groups in the
+pattern.
+
+Examples:
+^^^^^^^^^
+
+Many user names are of the form "user@domain", to split the username
+from the domain and to be able to work with those values independently
+use a regular expression and then assign the results to a variable. In
+this example there are two regular expression groups, the first group
+is the username and the second group is the domain. In the first
+example we use named groups and then access the match information in
+the special variable ``$regexp_map`` via the name of the group.
+
+::
+
+ ["regexp", "$assertion[UserName]", "(?P<username>\\w+)@(?P<domain>.+)"],
+ ["continue", "if_not_success"],
+ ["set", "$username", "$regexp_map[username]"],
+ ["set", "$domain", "$regexp_map[domain]"],
+
+
+This is exactly equivalent but uses numbered groups instead of named
+groups. In this instance the group matches are stored in the special
+variable ``$regexp_array`` and accessed by numerical index.
+
+::
+
+ ["regexp", "$assertion[UserName]", "(\\w+)@(.+)"],
+ ["continue", "if_not_success"],
+ ["set", "$username", "$regexp_array[1]"],
+ ["set", "$domain", "$regexp_array[2]"],
+
+
+
+--------------------------------------------------------------------------------
+
+regexp_replace
+--------------
+
+``regexp_replace $variable string pattern replacement``
+
+$variable
+ The variable which receives result of the replacement.
+
+string
+ The string to perform the replacement on.
+
+pattern
+ The regular expression pattern.
+
+replacement
+ The replacement specification.
+
+**regexp_replace** replaces each occurrence of ``pattern`` in
+``$string`` with ``replacement``. See `regexp`_ for details of using
+regular expressions.
+
+Examples:
+^^^^^^^^^
+
+Convert hyphens in a name to underscores.
+
+::
+
+ ["regexp_replace", "$name", "$name", "-", "_"]
+
+
+--------------------------------------------------------------------------------
+
+split
+-----
+
+``split $variable string pattern``
+
+$variable
+ This variable is assigned an array containing the split items.
+
+string
+ The string to split into separate items.
+
+pattern
+ The regular expression pattern used to split the string.
+
+**split** splits ``string`` into separate pieces and assigns the
+result to ``$variable`` as an array of pieces. The split occurs
+wherever the regular expression ``pattern`` occurs in ``string``. See
+`regexp`_ for details of using regular expressions.
+
+Examples:
+^^^^^^^^^
+
+Split a list of groups separated by a colon (:) into an array of
+individual group names. If $assertion[Groups] contained the string
+"user:admin" then $group_list will set to ["user", "admin"].
+
+::
+
+ ["split", "$group_list", "$assertion[Groups]", ":"]
+
+
+
+--------------------------------------------------------------------------------
+
+join
+----
+
+``join $variable array join_string``
+
+$variable
+ This variable is assigned the string result of the join operation.
+
+array
+ An array of string items to be joined together with
+ ``$join_string``.
+
+join_string
+ The string inserted between each element in ``array``.
+
+**join** accepts an array of strings and produces a single string
+where each element in the array is separated by ``join_string``.
+
+Examples:
+^^^^^^^^^
+
+Convert a list of group names into a single string where each group
+name is separated by a colon (:). If the array ``$group_list`` is
+["user", "admin"] and the ``join_string`` is ":" then the
+``$group_string`` variable will be set to "user:admin".
+
+::
+
+ ["join", "$group_string", "$groups", ":"]
+
+
+--------------------------------------------------------------------------------
+
+lower
+-----
+
+``lower $variable value``
+
+$variable
+ This variable is assigned the result of the lower operation.
+
+value
+ The value to lower case, may be either a string, array, or
+ associative array.
+
+**lower** lower cases the input value. The input value may be one of
+the following types:
+
+string
+ The string is lower cased.
+
+array
+ Each member of the array must be a string, the result is an array
+ with the items replaced by their lower case value.
+
+associative array
+ Each key in the associative array is lower cased. The values
+ associated with the key are **not** modified.
+
+Examples:
+^^^^^^^^^
+
+Lookup ``UserName`` in the assertion and set the variable
+``$username`` to it's lower case value.
+
+::
+
+ ["lower", "$username", "$assertion[UserName]"],
+
+Set each member of the ``$groups`` array to it's lower case value. If
+``$groups`` was ["User", "Admin"] then ``$groups`` will become
+["user", "admin"].
+
+::
+
+ ["lower", "$groups", "$groups"],
+
+To enable case insensitive lookup's in an associative array lower case
+each key in the associative array. If ``$assertion`` was {"UserName":
+"JoeUser"} then ``$assertion`` will become {"username": "JoeUser"}
+
+::
+
+ ["lower", "$assertion", $assertion"]
+
+--------------------------------------------------------------------------------
+
+upper
+-----
+
+``upper $variable value``
+
+$variable
+ This variable is assigned the result of the upper operation.
+
+value
+ The value to upper case, may be either a string, array, or
+ associative array.
+
+**upper** is exactly analogous to `lower`_ except the values are upper
+cased, see `lower`_ for details.
+
+
+--------------------------------------------------------------------------------
+
+in
+--
+
+``in member collection``
+
+member
+ The value whose membership is being tested.
+
+collection
+ A collection of members. May be string, array or associative array.
+
+**in** tests to see if ``member`` is a member of ``collection``. The
+membership test depends on the type of collection, the following are
+supported:
+
+array
+ If any item in the array is equal to ``member`` then the result is
+ success.
+
+associative array
+ If the associative array contains a key equal to ``member`` then
+ the result is success.
+
+string
+ If the string contains a sub-string equal to ``member`` then the
+ result is success.
+
+Examples:
+^^^^^^^^^
+
+Test to see if the assertion contains a UserName value.
+
+::
+
+ ["in", "UserName", "$assertion"]
+ ["continue", "if_not_success"]
+
+Test to see if a group is one of "user" or "admin".
+
+::
+
+ ["in", "$group", ["user", "admin"]]
+ ["continue", "if_not_success"]
+
+Test to see if the sub-string "BigCorp" is in
+the assertion's ``Provider`` value.
+
+::
+
+ ["in", "BigCorp", "$assertion[Provider]"]
+ ["continue", "if_not_success"]
+
+
+--------------------------------------------------------------------------------
+
+not_in
+------
+
+``in member collection``
+
+member
+ The value whose membership is being tested.
+
+collection
+ A collection of members. May be string, array or associative array.
+
+**not_in** is exactly analogous to `in`_ except the sense of the test
+is reversed. See `in`_ for details.
+
+--------------------------------------------------------------------------------
+
+compare
+-------
+
+``compare left operator right``
+
+left
+ The left hand value of the binary operator.
+
+operator
+ The binary operator used for comparing left to right.
+
+right
+ The right hand value of the binary operator.
+
+
+**compare** compares the left value to the right value according the
+operator and sets success if the comparison evaluates to True. The
+following relational operators are supported.
+
++----------+-----------------------+
+| Operator | Description |
++==========+=======================+
+| == | equal |
++----------+-----------------------+
+| != | not equal |
++----------+-----------------------+
+| < | less than |
++----------+-----------------------+
+| <= | less than or equal |
++----------+-----------------------+
+| > | greater than |
++----------+-----------------------+
+| >= | greater than or equal |
++----------+-----------------------+
+
+
+The left and right hand sides of the comparison operator *must* be
+the same type, no type conversions are performed. Not all combinations
+of operator and type are supported. The table below illustrates the
+supported combinations. Essentially you can test for equality or
+inequality on any type. But only strings and numbers support the
+magnitude relational operators.
+
+
++----------+--------+---------+------+---------+-----+------+------+
+| Operator | STRING | INTEGER | REAL | BOOLEAN | MAP | LIST | NULL |
++==========+========+=========+======+=========+=====+======+======+
+| == | X | X | X | X | X | X | X |
++----------+--------+---------+------+---------+-----+------+------+
+| != | X | X | X | X | X | X | X |
++----------+--------+---------+------+---------+-----+------+------+
+| < | X | X | X | | | | |
++----------+--------+---------+------+---------+-----+------+------+
+| <= | X | X | X | | | | |
++----------+--------+---------+------+---------+-----+------+------+
+| > | X | X | X | | | | |
++----------+--------+---------+------+---------+-----+------+------+
+| >= | X | X | X | | | | |
++----------+--------+---------+------+---------+-----+------+------+
+
+
+Examples:
+^^^^^^^^^
+
+Test to see if the ``$groups`` array has at least 2 members
+
+::
+
+ ["length", "$group_length", "$groups"],
+ ["compare", "$group_length", ">=", 2]
+
+
+--------------------------------------------------------------------------------
+
+exit
+----
+
+``exit status criteria``
+
+status
+ The result for the rule.
+
+criteria
+ The criteria upon which will cause the rule will be immediately
+ exited with a failed status.
+
+**exit** causes the rule being executed to immediately exit and a rule
+result if the specified criteria is met. Statement verbs such as `in`_
+or `compare`_ set the result status which may be tested with the
+``success`` and ``not_success`` criteria.
+
+The exit ``status`` may be one of:
+
+rule_fails
+ The rule has failed and no mapping will occur.
+
+rule_succeeds
+ The rule succeeded and the mapping will be applied.
+
+The ``criteria`` may be one of:
+
+if_success
+ If current result status is success then exit with ``status``.
+
+if_not_success
+ If current result status is not success then exit with ``status``.
+
+always
+ Unconditionally exit with ``status``.
+
+never
+ Effectively a no-op. Useful for debugging.
+
+Examples:
+^^^^^^^^^
+
+The rule requires ``UserName`` to be in the assertion.
+
+::
+
+ ["in", "UserName", "$assertion"]
+ ["exit", "rule_fails", "if_not_success"]
+
+--------------------------------------------------------------------------------
+
+
+continue
+--------
+
+``continue criteria``
+
+criteria
+ The criteria which causes the remainder of the *block* to be
+ skipped.
+
+**continue** is used to control execution for statement blocks. It
+mirrors in a crude way the `if` expression in a procedural
+language. ``continue`` does *not* affect the success or failure of a
+rule, rather it controls whether subsequent statements in a block are
+executed or not. Control continues at the next statement block.
+
+Statement verbs such as `in`_ or `compare`_ set the result status
+which may be tested with the ``success`` and ``not_success`` criteria.
+
+The criteria may be one of:
+
+if_success
+ If current result status is success then exit the statement
+ block and continue execution at the next statement block.
+
+if_not_success
+ If current result status is not success then exit the statement
+ block and continue execution at the next statement block.
+
+always
+ Immediately exit the statement block and continue execution at the
+ next statement block.
+
+never
+ Effectively a no-op. Useful for debugging. Execution continues at
+ the next statement.
+
+Examples:
+^^^^^^^^^
+
+The following pseudo code:
+
+::
+
+ roles = [];
+ if ("Groups" in assertion) {
+ groups = assertion["Groups"].split(":");
+ if ("qa_test" in groups) {
+ roles.append("tester");
+ }
+ }
+
+could be implemented this way:
+
+::
+
+ [
+ ["set", "$roles", []],
+ ["in", "Groups", "$assertion"],
+ ["continue", "if_not_success"],
+ ["split" "$groups", $assertion[Groups]", ":"],
+ ["in", "qa_test", "$groups"],
+ ["continue", "if_not_success"],
+ ["append", "$roles", "tester"]
+ ]