Security Introduction

Overview

The v5.0 (Fortress) release has seen the overhaul of the FarCry Security model to incorporate content type modelling, increased extensibility and a completely new user interface for interacting with security elements.

Key Requirements

  • The whole schema should be driven by content types
  • It must be easy to extend the framework to allow for LDAP, ActiveDirectory and OpenID integration
  • Examples of each supported external directory type should be provided, perhaps as plugins
  • When mapping groups to roles it may be necessary to allow developers to nominate their own method for listing user directory groups (especially in those situations where the external directory has 1000's of groups)
  • permission checks need to be cached by role
  • replace all UI elements
  • ensure multiple user directories work
  • Integrate Audit sub-section for reporting
  • Clearly define the "permission set" construct (review webskin permissions)
  • Provide scaffolding for permission sets
  • remove security cache from server scope

User Directories

The purpose of a user directory is to authenticate users and provide information on their group membership.

With FarCry 5.0 the way user directories are implemented has been changed. In previous versions user directories were mixed into FarCry's internal permission and security structure. This made it difficult for people to add another user directory. Pulling them apart makes it easier to add something like an OpenID login, or an LDAP user directory. There are some disadvantages: functionality where FarCry depends on intimate knowledge of the user directory (such as being able to get a full list of users) are no longer possible.

Structure of a user directory

A user directory needs to extend the farcry.core.packages.security.UserDirectory component.

You can also extend another user directory to change the settings or the way it behaves. For example, the FarcryUD user directory uses a variable called "encrypted" to flag whether passwords should be hashed in the database. By default it is set to false, but you can extend FarcryUD and set it to true in the init method.

Your user directory will be identified in the UI by it's title attribute. If that isn't specified, the displayname will be used.

The value of the key attribute is used in internal variables and structures. If that isn't specified, the component name will be used.

init()

This isn't necessary, but may be useful as it is called when the user directory component is instantiated. Note: if you do implement this method, it should call super.init(): it copies the component attributes (key, title, and whatever else a UD needs) into the component scope for easier access.

getLoginForm()

The security model uses form components to encapsulate the login form for a user directory. This method should return the name of the form. e.g. FarcryUD returns "farLogin". More about the login form is explained below in authenticate()

The login form component should not include a process function as all processing should be done in the authenticate() function. The purpose of the form is simple to define the fields and provide a hook for the displayLogin webskin which allows you to skin the login page.

The code for the displayLogin webskin boils down to:

<cfimport taglib="/farcry/core/tags/formtools/" prefix="ft" />

<ft:form>
  <ft:object typename="farLogin" />

  <cfif isdefined("arguments.stParams.message") and len(arguments.stParams.message)>
    <cfoutput>
      <div class="error">#arguments.stParams.message#</div>
    </cfoutput>
  </cfif>

  <ft:farcryButton value="Log In" />
</ft:form>

Notice that the webskin has access to authentication errors via arguments.stParams.message.

and authenticate() to:

<cfset result = structnew() />

<cfimport taglib="/farcry/core/tags/formtools" prefix="ft" />

<ft:processform>
	<ft:processformObjects typename="#getLoginForm()#">
		<!--- Authenticate user and update result --->
	</ft:processformObjects>
</ft:processform>

<cfreturn result />

Basically: the form component and webskin outputs a form, authenticate processes the form.

authenticate()

The authenticate function is called every time the login page is requested. If it detects that its login form was submitted, it should attempt to authenticate the user and return the result.

Return struct
Note: Return an empty struct if no authentication was attempted

Key

Description

userid

If the user is successfully authenticated, this should be a user id unique for the user directory

authenticated

True on success, false on failure

message

On authentication failure this message is passed to the login form

getUserGroups(userid)

Returns an array of the groups the specified user is a member of.

NOTE: groups should not contain '_' (underscore) characters, as users and groups are identified as userid_ud and group_ud outside the user directory.

getAllUserGroups()

Returns an array list of all the groups this user directory supports.

NOTE: groups should not contain '_' (underscore) characters, as users and groups are identified as userid_ud and group_ud outside the user directory.

getGroupUsers(group)

Returns an array of the userid's of members of the specified group.

getProfile(userid)

This function is called on login to update the profile properties of the user. The contents of the returned struct are appended directly to the dmProfile properties struct.

The returned struct is checked for an 'override' key. If it is set to true, the profile is overwritten with the returned values. If not, the returned struct is effectively only used when creating the profile on first login.

Overview profile details and options

Profile details (e.g. name, locale) and options (edit profile, change password) are displayed in the sidebar of the FarCry webtop Overview page.

Change password is functionality that may not be supported by certain user directories within FarCry. For example some OpenID services don't use passwords at all. To add UD specific options, create a displaySummaryOptionsUD.cfm webskin for dmProfile in your plugin or project (where "UD" is the key for your user directory) and add LI items for each option.

<cfsetting enablecfoutputonly="true" />
<!--- @@displayname: Summary options (Example) --->
<!--- @@description: Example UD specific options --->

<cfset stUser = createObject("component", application.stcoapi["farUser"].packagePath).getByUserID(listfirst(stObj.username,"_")) />

<cfoutput>
  <li>
    <small>
      <a href="#application.url.farcry#/changepassword.cfm">Change password</a>
    </small>
  </li>
</cfoutput>

<cfsetting enablecfoutputonly="false" />

To add details to the profile summary, create a displaySummaryDetailsUD.cfm webskin.

<cfsetting enablecfoutputonly="true" />
<!--- @@displayname: Summary details (EXAMPLE) --->
<!--- @@description: Example UD specific details --->

<cfoutput>
  <dt>User directory</dt>
  <dd>Example User Directory</dd>
</cfoutput>

<cfsetting enablecfoutputonly="false" />

Customising FarcryUD

The FarcryUD user directory is the next iteration of FarCry's internal user store. There are a number of ways to customise it.

Password Encryption

By default passwords are stored as plain text. This can be changed by extending FarcryUD in your project and adding bEncrypted="true" as an attribute to the component.

Login page

Add a displayLogin webskin to the farLogin form in your project (the same way you add webskins to types and rules). This webskin should output the form using ft:form and ft:object.

If your project uses more than one user directory, you will also need to use the sec:SelectUDLogin tag. This tag will generate a list of user directories for the user to select from, and will display the correct login form for the selected UD.

Permissions

The Permission content type

Most permissions are defined using the farPermission content type, the replacement for dmPermission. These are used to define the name of a permission and indicate whether it can be attached to particular objects in the database. This flexibility allows the generic farPermission type to support three different types of permission.

Object Permissions

In theory these permissions can be applied to any object of any type in the database, but at this point only dmNavigation is supported by the UI.

Object permissions correspond to actions within the site overview tree. Tree permissions are defined at a specific node in the tree and then all child nodes inherit their permissions from the nearest parent with permissions set.

e.g. the 'Edit' object permission allows a site admin to grant edit access to a department branch of the website, or remove anonymous view access from a member only branch.

Type (or permission set) Permissions

These are groups of generic permissions that are used for basic type access. A type's permission set contains the following permissions: Approve, CanApproveOwnContent, Create, Edit, Delete, and RequestApproval.

Most types do not have a permission set by default (one exception is news). A content type without its own permission set honours the Generic permission set.

You can create a permission set manually by adding permissions named in the form "#typename##permission#" to the security model. A simpler approach is to use the permission set scaffold on the COAPI types page. With the scaffold a developer can create a type's permission set, and manage the roles those permissions are assigned to.

General Permissions

A general permission is an arbitrary permission. Permissions like webtop access are general permissions.

Webskin Permissions

These have been added as of FarCry 5.0.

A webskin permission essentially represents the right of a user to run or execute a specific view - or webskin. If a user does not have permission to run a specific webskin then a simple semantic message indicating denied access is shown. This is done using the deniedaccess webskin, which can be overriden for all content types in a project or just one.

Webskin permissions are dynamically determined on application initialisation by examining the webskin permission rules assigned to a specific role and determining what permissions are allowed. Role webskin rules are similar in syntax to objectbroker webskin caching but include options for nominating a specific type or applying the filter globally.

A permission check is then performed for each request to getView() and the webskin tag view.

Examples

Filter

Result

*

Allow all webskins for all types, rules, and forms

*.displayPage*

Allow all webskins prefixed with displayPage for all types, rules, and forms

displayPage*

Equivilent to *.displayPage*

dmHTML.*

Allow all webskins for the dmHTML component

dmEvent.display*

All all webskins prefixed with display for dmEvent

ruleNews.execute

Allow the execute webskin on ruleNews

Webskin permissions are managed from within a role.

Permission Checks

Preferred method of checking permissions is the CheckPermission tag.
Permission checks are performed by a component in the application scope. Each time a check is made the result is cached in the component, keyed by the role.

To check a permission use application.security.checkPermission. A list of roles can also be passed in with the role argument, but the system uses the roles of the current logged in use by default or just the default roles if no one is logged in.

Generic permission
application.security.checkPermission(permission="Admin")
Item specific permission (e.g. tree node)
application.security.checkPermission(permission="View",object=request.navid)
Webskin permission
application.security.checkPermission(webskin="displayPageStandard")

Upgrading from 4.0

Because security will be managed in a new structure a catch-22 situation exists when it comes to migrating the data:

  1. this script migrates pre 5.0 security data to the new structure
  2. to access the updates directory you have to log in
  3. login uses the new structure
  4. which is empty until this script migrates the security data

So a directory has been added to upgrades: "b410_security". As part of the upgrade to 4.1 this directory needs to be copied to the www directory of your project where access isn't restricted, loaded in a web browser, then deleted.

All users and groups are migrated to the FarcryUD user directory, and policy groups / permissions are migrated to the new role and permission content types. Note that by default administration roles are granted access to all webskins, but anonymous only has access to display* and execute*.