Form Runner Access Control


Overview 

When it comes to access control, Orbeon Forms leverages and delegates as much as possible to your existing security infrastructure:
  • You define users and roles outside of Orbeon Forms.
  • Whenever possible, access control is path-based, so you can define who can access what based on the path in the URL with your existing security infrastructure.
You can have access control at two levels:
  • Form level – Can the current user access this form?
  • Field level – If the current user can access the form, can they access a particular field? If they can, can they change the field or just read its value?
You implement the later in your form definition, by using the $fr-roles in the in the visibility and read-only XPath expressions of the Form Builder control validation dialog. You can make a control non-visible to the current user by defining a visibility expression that returns false. If the control is visible, you can make it read-only to current user by defining a read-only expression that returns true. The rest of this page focuses on form-level access control.

Accessing the username and roles

[SINCE: 2011-05-18]
  • Username/role from headers or container — Orbeon Forms automatically adds two headers, which are available within Orbeon Forms applications, in particular Form Runner:
  • Orbeon-Username — if present, the value contains the current username
    • if oxf.fr.authentication.method == "container":
      • obtained through the servlet/portlet container's getRemoteUser() function
    • if oxf.fr.authentication.method == "header"
      • obtained via the header specified by oxf.fr.authentication.header.username
  • Orbeon-Roles — if present, is a list of values, each with one role
    • if oxf.fr.authentication.method == "container":
      • each role listed in oxf.fr.authentication.container.roles is check against the container's isUserInRole() function
    • if oxf.fr.authentication.method == "header"
      • obtained via the header specified by oxf.fr.authentication.header.roles
  • Forwarding headers — When using header-based authentication, in addition to defining the name of the headers Form Runner gets the username and role from oxf.fr.authentication.header.username and oxf.fr.authentication.header.roles, you need to add those header names to the oxf.xforms.forward-submission-headers property, so the headers are forwarded by the XForms engine to Form Runner. For instance:

    <property as="xs:string" name="oxf.xforms.forward-submission-headers"
                             value="My-Username-Header My-Roles-Header"/>

  • Persistence — These headers are forwarded to the persistence layer, which can make use of them. In particular, the Oracle and MySQL persistence layers stores the current username when doing any database update.


Access control for deployed forms 

With Orbeon Forms 3.9

You control access to forms entirely based on paths. For this discussion, let's assume that:
  1. You deployed Orbeon Forms on /orbeon. (You can choose to deploy Orbeon Forms on any context, including just on /.)
  2. You have two applications (or group of forms), which correspond to two services of your company: hr and sales.
  3. HR has a number of forms including one called expense-report.

Given this, here is how paths will look like:

Path
Description
/orbeon/fr/hr/expense-report/new To create a new expense report.
/orbeon/fr/hr/expense-report/edit/{id} To edit an expense report having that given, system-generated id.
/orbeon/fr/hr/expense-report/summary To view all the submitted expense reports.


Now, consider the following access rules you might want to put in place:

What you want
How to implement it
All the employes should be able to create a new expense report. Give access to /orbeon/fr/hr/expense-report/new to any authenticated employee.
Only HR persons should be able to view or edit submitted expense reports. Restrict access to /orbeon/fr/hr/expense-report/edit/* and /orbeon/fr/hr/expense-report/summary to authenticated users with the appropriate "HR" role defined in your authentication system.
Only persons in the sales department should be able to access the any of the forms in the sales app. Restrict access to /orbeon/fr/sales/* to authenticated users with the appropriate "sales" role defined in your authentication system.

One possible way is to do this within the web.xml file:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Expense report: new</web-resource-name>
        <url-pattern>/fr/hr/expense-report/new</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>orbeon-user</role-name>
    </auth-constraint>
</security-constraint>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Expense report: summary and edit</web-resource-name>
        <url-pattern>/orbeon/fr/hr/expense-report/summary</url-pattern>
        <url-pattern>/fr/hr/expense-report/edit/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>orbeon-hr</role-name>
    </auth-constraint>
</security-constraint>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Sales forms: all pages</web-resource-name>
        <url-pattern>/fr/sales/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>orbeon-sales</role-name>
    </auth-constraint>
</security-constraint>

For more information about this web.xml configuration, please refer to the Java EE documentation, for example this tutorial.

Since Orbeon Forms 4.0

[SINCE 2011-07-22]

Enabling permissions

For forms created in Form Builder, you can restrict which users can access which forms, and what operations they can perform. Those restrictions apply to the forms you create once they are deployed, not to editing those forms in Form Builder (for the latter, see the section that follows: Access control for editing forms).

By default, no restriction is imposed on who can do what with forms you create in Form Builder. You enable permissions by going to the Form Builder sidebar, and under Advanced, clicking on Set Permissions. This shows the following dialog:



Setting permissions

After you click on the checkbox, you'll be able to set access restriction on the create, read, update, and delete operations:
  1. On the Anyone  line, set the operations allowed to all users.
  2. [SINCE 4.3] On the Owner line, set the operations allowed to the user who created the data.
  3. [SINCE 4.3] On the Group members line, set the operations allowed to users in the same group as the owner.
  4. On the following lines, you can enter a role name, and define what operations users with that role can perform.

Example

In the example below:
  • Any user to fill out a new form.
  • Users with the role clerk to read  data.
  • Users with the role admin to do any operation, including deleting form data.



Permissions dialog

  • Permissions you set in the dialog are additive – Say you defined permissions for two roles, where users with the reader role can read and users in the clerk role can delete, users with both roles (reader and clerk) are allowed to perform both operations (reading and deleting).
  • Operation on Anyone apply to all other rows – When you select a checkbox for a given operation on the first Anyone row, that checkbox will be automatically checked and disabled so you can't change it, for any additional row, since you wouldn't want to authorize users with additional roles to perform less operations.
  • Update implies read – On any row, if you check update, then read will be automatically checked, as it wouldn't make sense to say that some users can update data, but can't read it, as when updating data, obviously, they must be shown the data they are updating.
  • Create can't be set for the owner and group members – The owner/group is a piece of information attached to existing form data, keeping track of the user who create the data, and the group in which this user is. This information is only known for existing data, so assigning the create permission to the owner or group members doesn't make sense, and the dialog doesn't show that checkbox.
  • Permissions for the owner and group members can be set independently – If you want data to be accessible only by people who created it, check read/update/delete for the owner but not for group members. If you want data to be accessible by all people in the same group, check read/update/delete for the group members and don't check them for the owner if you want the owner to loose access to that data in case the owner changes group. (The latter highlights the need for permissions owner and group member to be set independently.)

Permissions for owner / group members

[SINCE 4.3 on MySQL and DB2] [SINCE 4.4 on Oracle] For now, permissions based on owner and group are only supported by the Oracle, MySQL, and DB2 persistence layers. With Orbeon Forms 4.4 and newer, you don't need to do anything special to use this feature. However, with Orbeon Forms 4.3 specifically, you need to:
  1. If using MySQL, update your database by running this  DDL. (The tables for DB2 on 4.3 already contain the required changes out-of-the-box.)
  2. Set the following properties:

    <property as="xs:boolean" name="oxf.fr.support-owner-group" value="true"/>
    <property as="xs:boolean" name="oxf.fr.support-autosave"    value="true"/>

Access restrictions

Which operations the current user can perform drives what page they can access, and on some pages which buttons are shown:
  • On the Form Runner home page, all the forms on which the current user can perform at least one operation are displayed. Then, for each one of those forms:
    • If they can perform the create operation on the form, a link to the new page is shown.
    • If they can perform any of the read, update, or delete operation on the form, a link to the summary page for that form is shown.
  • For the summary page:
    • Access is completely denied if the current user can't perform any of the read, update, or delete operations.
    • The delete button is disabled if the current user can't perform the delete operation.
    • The review and pdf button are disabled if the current user can't perform the read operation.
    • Clicking in a row of the table will open the form in edit mode if the current user can perform the update operation, in view mode if they can perform the read operation, and do nothing otherwise.
  • For the view page, access is denied if the current user can't perform the read operation.
  • For the new page, access is denied if the current user can't perform the create operation.
  • For the edit page, access is denied if the current user can't perform the update operation.

[SINCE 4.3] In 4.2 and earlier, role-based permissions set in Form Builder could only be driven by container-based roles and the value of the oxf.fr.authentication.method property was not taken into account. Since 4.3, those permissions also apply if you are using header-driven roles.

Access control for editing forms

Access to Form Builder  as a whole

Given the assumptions made in the previous section, the paths used by Form Builder look as follows:

Path
Description
/orbeon/fr/orbeon/builder/new To create a new form.
/orbeon/fr/orbeon/builder/edit/{id} To edit a form having that given, system-generated id.
/orbeon/fr/orbeon/builder/summary To view all the editable forms.

If you have multiple classes want to give access to Form Builder to one class of users, and those users are able to edit any form in any app, the you can use path-based access restrictions, as described the previous section.

Access to specific apps/forms in Form Builder (form-builder-permissions.xml)

If you'd like to have multiple classes of Form Builder users where some case edit, say, form in the hr app, while other can edit forms in the sales app, then you'll want to setup the form-builder-permissions.xml

NOTE: [SINCE 2011-09-07] The file is now called form-builder-permissions.xml file. It replaces the file called form-runner-roles.xml. For backward compatibility, form-runner-roles.xml is still supported.

In this file you map role names to applications and forms. For instance, the following tells Orbeon Forms that only users with the role hr-form-editor can edit or create forms in the hr app, and only users with the role sales-form-editor can edit or create forms in the sales app. As you can infer from the syntax, you could be even more specific and only give access to users with a given role to a specific form in a specific app.

<roles>
    <role name="hr-form-editor"    app="hr"    form="*"/>
    <role name="sales-form-editor" app="sales" form="*"/>
</roles>


Orbeon Forms can infer the roles for the current user either based on information it gets from the container or from an HTTP header. Those two cases are detailed in the following two sections. Once you've defined your form-builder-permissions.xml and done the appropriate setup for container-driven or header-driven roles, as described below:
  1. The Form Builder summary page will only show the forms users have access to.
  2. When they create a new form, if users don't have the right to create a form in any app, instead of seeing a text field where they can enter the application name, they will see a drop-down listing the possible application, as shown in the following screenshot:

[LIMITATION] Restrictions on the form name in form-builder-permissions.xml are at this point not supported; only restrictions on the app name are supported. This means that you should always use form="*". If you define a restriction on the form name, it won't be enforced at the time the form is created, allowing users to create, save, and publish a form with an undesirable name. However they then won't be able to see the form they created when going back to the summary page.

Form Runner Home page

[SINCE Orbeon Forms 4.3]

form-builder-permissions.xml also impacts the Form Runner Home page, which supports unpublishing and publishing forms.

If the configuration is unchanged, by default users cannot unpublish/publish from the Home page. In order to allow this feature, you must configure at least one <role>.

In general, the behavior is as follows:
  • by default no <role> elements are present
    • for the Form Builder Summary and New pages, this is equivalent to:
       <role name="*" app="*" form="*"/>
  • for the Form Runner Home page
    • no <role> elements
      • user cannot unpublish/publish any forms
      • user cannot see unavailable forms
    • with <role name="*" app="*" form="*"/>:
      • user can unpublish/publish any forms
      • user sees unavailable forms

This logic ensures:
  • that Form Builder is usable out of the box even without setting Form Builder permissions
  • that the Form Runner Home page, which can by default be accessed by any user unless it is explicitly protected, does not inadvertently provide access to administrative functions

Username, group, and roles

Form Runner uses the username, group, and roles to control who can access Form Builder and the forms you create with Form Builder (see the two sections above for more details on how those are setup). Form Runner can obtain this information by calling a standard servlet API implemented by your application server (container-driven method)  or by using HTTP headers (header-driver method), typically set by a front-end (e.g. Apache module) or a servlet filter.

Container-driven method

[SINCE 2011-07-01]

You want to use container roles if your users are setup at the application server level, with container managed security. In Tomcat, this would correspond to using a security realm, which in its simplest form gets users from Tomcat's conf/tomcat-users.xml. To setup container-driven roles, configure your form-builder-permissions.xml as described above, then:
  1. Enable container-driven method – To do so, set the following property in your properties-local.xml:

    <property as="xs:string" name="oxf.fr.authentication.method" value="container"/>

  2. List possible roles – There is no container API for Orbeon Forms to ask for all the roles for the current user; instead Orbeon Forms can only ask if the current user has a specific role. Because of this, you need to list the possible roles in the following property. For instance, if you have two roles form-builder-hr and form-builder-finance define it as:

    <property as="xs:string" name="oxf.fr.authentication.container.roles"
                             value="form-builder-hr form-builder-finance"/>

    There is no container API for Orbeon Forms to get the user's group; in fact the concept of group is foreign the container API. So, when using container-driven method, Orbeon Forms takes the first role listed in oxf.fr.authentication.container.roles that the user has as the user's group. If you need more flexibility in determining what the user's group is, you might want to use header-driven method instead, which allows you to explicitly set through a header what the user's group is (more on this below).

  3. Require authentication – You'll also want to have another role, say form-builder, that you grant to all the users who can access Form Builder. Hence, in our example, users will have either the two roles form-builder and form-builder-hr, or the two roles form-builder and form-builder-finance. In Orbeon Forms WEB-INF/web.xml, add the following to require users to login to access Form Builder. This assumes that you're using basic authentication:

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Form Builder</web-resource-name>
            <url-pattern>/fr/orbeon/builder/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>form-builder</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
    </login-config>
    <security-role>
        <role-name>form-builder</role-name>
    </security-role>

Header-driven method

You want to use header-driven method if you have a servlet filter, single sign-on software, or other system that sets the roles for the current user in an HTTP header. To use header-driven method, configure your form-builder-permissions.xml as described above, then:
  1. Enable header-driven method – To do so, set the following property in your properties-local.xml:

    <property as="xs:string" name="oxf.fr.authentication.method" value="header"/>

  2. Header name – Tell Orbeon Forms what is the name of the HTTP headers that will contain the username, group, and roles for the current user.

    <property as="xs:string" name="oxf.fr.authentication.header.username" value="My-Username-Header"/>
    <property as="xs:string" name="oxf.fr.authentication.header.group"    value="My-Group-Header"/>
    <property as="xs:string" name="oxf.fr.authentication.header.roles"    value="My-Roles-Header"/>

    The header oxf.fr.authentication.header.roles consists of  a list of comma- or pipe-separated role names, for example: "Administrator, Power User, User" or "Administrator | Power User | User". Whitespace around the commas or pipes is ignored. In addition or alternatively, multiple role headers can be provided, and each of them is split according to those roles. The resulting set of roles is the combination of all roles extracted from all role headers.

  3. LDAP-style header syntax (Optional) – The value of the header is a list of roles separated by spaces, commas, or pipes (|). Furthermore, they can optionally be composed of properties in the form of name=value, where name is specified by a configuration property, and value is the value of the role. This is typically useful the value if the header follows an LDAP-style syntax, for instance: cn=role1,dc=acme,dc=ch|cn=role2,dc=acme,dc=ch. If your header follows a LDAP-style syntax, set the following property to configure what "name" contains the header, which in this example is cn:

    <property as="xs:string" name="oxf.fr.authentication.header.roles.property-name" value="cn"/>