Permissions System

From Zymonic

Permissions, a Background Guide[edit]

Tables[edit]

Each table that needs securing has one or more RelationshipPermission Zymonic XML objects referenced in it. This will define who can access the data and under what circumstances; before looking at the XML we need to understand the concepts that are used within Zymonic's security system.

Users[edit]

Zymonic's users are unremarkable - generally there is one zz_user_record entry associated with each person (or remote system). Very often the zz_user_record entries are created / maintained using a PerformTransition/PerformUpdates action on a separate 'user' process where significant customisations or legacy systems are integrated.

Zymonic users can also be automatically populated/synchronised with an external directory system e.g. LDAP or eDirectory - these modes can be enabled in SystemOptions XML using the guides in the POD of the appropriate modules e.g. "man Zymonic::Auth::LDAP".

Groups[edit]

Groups are held in zz_groups.

Whilst groups can be used in a similar fashion to other permission systems it is important to consider whether a group or a role is most appropriate. If you are trying to group users by what they can access, i.e., which records they should have access to, then a group is appropriate. If there is no link between the zz_groups table and the record being secured (a foreign key of some sort) then the functionality should probably be being implemented with a role.

Roles[edit]

Roles are where groups of users can do the same things within Zymonic even if they can't do the same things to different records within the same table (in this case a 'Group' with some kind of link to the table becomes appropriate - probably in addition to a role rather than instead of).

Roles are maintained via the 'Roles List'.

Each Role has a name and a seniority. The smaller the seniority is the more senior the user is. If a user has a role which allows them to create or maintain roles then they may only create/maintain roles with a lower seniority to their own role; additionally if a user has sudo permission then the user can only become a user who has a role that is equal or lower in senority, i.e., numerically higher.

Each role also has a list of 'Role Permissions' these can be enabled or disabled on the role. The user will only be able to enable and see role permissions that their own role has.

When the config build is run a 'superuser' role will be created/updated which has all role permissions enabled - this is the role which will be needed to start creating other roles.

The user's role is set on their zz_user_record record.

zz_record_security[edit]

For every record in a DB table in Zymonic there are permissions that determine who can access that record (read write etc.) to determine that, each record has a ‘sec_id’ field, this field can contain;

A) nothing - means no-perms anyone can do anything with the record - unusual

B) a word/number that refers to the table and is the same for most/all records in that table

C) a word/number that is unique to that record or a small number of records

in the case of B or C Zymonic will use the sec_id to:

OLD MODE: Look up records in zz_record_security (sec_id being a ‘foreign key’ in there)

NEW MODE: Look up records in zz_[table being secured’s name]_rs - (sec_id being a foreign key in there)

In either case those records will contain:

| Field                   | Type          | Null | Key | Default             | Extra        |                
|  permission_holder       | varchar(255)  | YES  | MUL | NULL                |                             |
| id                      | int(11)       | NO   | PRI | NULL                | auto_increment              |
| ip_list                 | varchar(255)  | YES  | MUL | NULL                |                             |
| fap_list                | varchar(255)  | YES  | MUL | NULL                |                             |
| tab                     | char(1)       | YES  | MUL | NULL                |                             |
| roleperm                | char(50)      | YES  | MUL | any                 |                             |
| field_zname             | varchar(255)  | YES  |     | NULL                |                             |
| sec_id                  | varchar(500)  | YES  | MUL | NULL                |                             |
|  has_field_permissions   | char(1)       | YES  |     | NULL                |                             |
| parent_process_id       | int(18)       | YES  | MUL | NULL                |                             |
| deleted                 | char(1)       | YES  | MUL | NULL                |                             |
| autocreated             | int(18)       | YES  | MUL | NULL                |                             |
| posix_process_id        | int(18)       | YES  |     | NULL                |                             |
| zzlu                    | timestamp     | NO   | MUL | CURRENT_TIMESTAMP   | on update CURRENT_TIMESTAMP |
| zzluts                  | double        | YES  |     | NULL                |                             |
| zza                     | timestamp     | NO   |     | 0000-00-00 00:00:00 |                             |
| zz_source               | varchar(512)  | YES  |     | NULL                |                             |
| zz_remote_keyfields     | varchar(1000) | YES  |     | NULL                |                             |
| permission_readable     | char(1)       | YES  |     | NULL                |                             |
| permission_deleteable   | char(1)       | YES  |     | NULL                |                             |
| permission_secureable   | char(1)       | YES  |     | NULL                |                             |
| permission_undeleteable | char(1)       | YES  |     | NULL                |                             |
| permission_appendable   | char(1)       | YES  |     | NULL                |                             |
| permission_changeable   | char(1)       | YES  |     | NULL                |                            

id, parent_process_id, deleted, autocreated, posix_process_id, zzlu, zzluts, zza, zz_source and zz_remote_keyfields - are all 'internal' fields and will not be considered further in this discussion.

permission_holder - Who is being allowed access : anyone authenticated (AUTHENTICATED), specific group (GROUP: [group id or name]), specific person or unauthenticated users (USER: [user id]) or someone not logged in (UNAUTHENTICATED)

ip_list - this is name of a list of IP addresses and ranges that is maintained in the zz_ip_lists table. That table will use ip_list as a 'foreign key' to check ip addresses against.

fap_list - like ip_list this is the foreign key in another table, zz_fap_lists, which contains a list of filters and processes to which this permission record applies.

tab - used to determine whether the sec_id record applies at table level (this flag is only used during refreshing the permissiosn during a config build).

roleperm* - This is the zname of the 'role permission' that the user must have (via their role) for this permission record to apply to them.

has_field_permissions - for records where individual fields have different permissions thie will be 'Y' and additional records will be retrieved which have the correct field_zname

field_zname - this indicates that the permissions apply to a particular field.

permission_* - these fields determine what the user can do, as well as the standard permissions (read, delete, secure, undelete, append, modify), additional permission types can be added in the XML or system configuration.

The security system will use the current session's details to determine which of the records retrieved from zz_record_security / zz_[table name]_rs apply to you and then give you all the permissions that you have at least one ‘yes’ for.

This last bit can be done either via a set of SQL joins / sub-query that are provided by Zymonic::Auth and then added as part of building the query or it can be done by passing a sec_id for one record to a Zymonic::Auth method that calculates the permissions for one record - this latter is done when loading one record e.g. on a single page form / when updating.

How Permissions influence Pages and their content / menus[edit]

Each process or filter has an entry in the zz_fap table; this links up to record security via a sec_id as usual, however, instead of being populated as normal based on RelationshipPermissions XML defined against the table, a special class of RelationshipPermission is associated with zz_fap which looks at the base table of the Filter or the base tables of the Forms within a Process and uses those to determine whether a user will be able to access any data via the process or filter. If a user will be able to access data via a filter or process then there will be an appropriate entry in the record security table.

Menus (Filters of Filters and Processes) use the zz_fap table to determine which processes / filters to show - they are generally limited by something in addition to security e.g. a specific list or a category (another field available within zz_fap).

Additionally a block will only show on a user's view of a page if they have a read permission on the filter or processes' entry in zz_fap.

Pages are also stored in a table (zz_pages) which has permissions.

Finally each page is automatically assigned a role permission and relationship permission.

A user will be able to see a page IF their role has the role permission for the page OR some other RelationshipPermission added to zz_pages allows them access AND there will be at least one visible block on the page.

This means that even if the user has the page permission, if they do not have permissions to access any of the data via any of the processes/filters in the menu on the page then they will not be able to see the page.

sudo[edit]

Like unix sudo allows a user to interact with the system as if they were another user (with an equal or less senior role) all actions they take when sudo-ed will be logged with both usernames.

RelationshipPermissions[edit]

Very Simple Case[edit]

This will give admin permissions to anyone logged in from any IP using any process!

 <RelationshipPermissions>
   <ZName>svz_admin_rp</ZName>
   <DisplayName>Admin Permissions</DisplayName>
   <IPList>any</IPList>
   <PermissionHolderType>authenticated</PermissionHolderType>
   <readable>true</readable>
   <changeable>true</changeable>
   <deleteable>true</deleteable>
   <secureable>true</secureable>
   <appendable>true</appendable>
 </RelationshipPermissions>

Limit Access with a Relationship to the Data and a Role Permission[edit]

  <RelationshipPermissions>
    <ZName>cdc_rp_user_maintain_clients</ZName>
    <DisplayName>Allow editing/deletion of clients for selected users.</DisplayName>
    <PermissionHolderType>group</PermissionHolderType>
    <RolePermission>
      <ZName>cdc_rolep_maintain_clients</ZName>
      <DisplayName>Allow user with this role permission to maintain client information</DisplayName>
    </RolePermission>
    <Join>
      <Table>
        <ZName>cdc_t_clients</ZName>
      </Table>
      <JoinCondition>cdc_t_clients.id = zz_groups.id AND zz_groups.ebex_f_source_table = 'cdc_t_clients'</JoinCondition>
    </Join>
    <Additionals>
      <ZName>cdc_rpa_user_maintain_clients</ZName>
      <WhereClause>cdc_t_clients.id IS NOT NULL</WhereClause>
    </Additionals>
    <readable>true</readable>
    <changeable>true</changeable>
    <deleteable>true</deleteable>
    <secureable>true</secureable>
    <appendable>false</appendable>
  </RelationshipPermissions>

The Security Toolkit[edit]

The security toolkit allows you to inspect exactly how any given record will be treated and allows resetting of permissions on items.

It is document either by doing:

 zymonic_toolkit.pl Security 

From the command line of a Zymonic installed machine or visiting: http://[domain]/man/[system]/Toolkit/Security.html of any of your systems.

Permissions 'A Style Guide'[edit]

The following is a work in progress, but it should give XML authors the basic principles of how to write permissions which result in the minimum number of entries in the role permission sub form of a role but still give maximum flexibility.

The general principle is that when we decide a user can do 'something' we are aiming for there to be one role permission that allows them to do this, even if that means multiple relationship permissions with that role permission.

We should never be in a position of having to tell a system administrator that, to create a role to do one 'thing' they will have to select multiple role permissions.

Basic Principles[edit]

Maintenance tables should have the auto-role permissions - No other table should have them1.

Processes with anything other than a standard auto process will almost certainly need a role permission per transition - and a role permission to 'view' the process - again it would generally not make sense* to use auto-role permissions on any of the base tables of the forms or on any table updated by actions / sub-forms of the process.

Any tables with 'potentially confidential' or 'sensitive' fields should have field permissions.

Linked fields - if your table / process has any linked fields then you need to add a view permission to the source table using the autorolepermission (or manually added permission as appropriate)

1 We're aiming to minimise the number of role permissions; and thus limit them to the set that is necessary to achieve all the roles required.

Through the remainder of this guide we make extensive use of a 'gift card' system's examples; the gift card process has customer, owner and scheme linked fields and uses actions to update the balances table. It has rules on updates that come from the scheme and are only visible to certain users and a number of other features that make it useful for describing the implementation of permissions.

Using the FAP lists[edit]

When a process needs to read (or write data) on a table that is not its 'main' table (e.g. anything other than the gift cards table for a gift card process), FAP Table entries can be used to ensure that when you use a RelationshipPermission to give access to the additional tables that those additional tables are only accessible via the correct process (e.g. the gift card process). FAP list entries can be created in bulk using a DefaultRecord CSV entry:


  <DefaultRecord>
    <ZName>gv_fap_list_entries</ZName>
    <Table>
      <ZName>zz_fap_lists</ZName>
    </Table>
    <CSV>
      <header>name,fap_zname</header>
      <line>gv_card,gv_card_process</line>
      <line>gv_history_reports,gv_card_process</line>
    </CSV>
  </DefaultRecord>

Linked Fields[edit]

When a linked field is on a form a readable permission should be added to the base table of the filter of the linked field using a FAP list entry. e.g.

    <RelationshipPermissions>
      <ZName>gv_customer_read_relp</ZName>
      <DisplayName>Allow users to view customers.</DisplayName>
      <RolePermission>
        <ZName>gv_card_people_rp</ZName>
      </RolePermission>
      <PermissionHolderType>authenticated</PermissionHolderType>
      <FAPList>gv_card</FAPList>
      <readable>true</readable>
      <changeable>false</changeable>
      <deleteable>false</deleteable>
      <secureable>false</secureable>
      <appendable>true</appendable>
    </RelationshipPermissions>

Note that in this case the gv_card_people_rp is the role permission used for a 'field permission' (see later) that allows users to see the customer and recipient of a gift card. If the linked field were one that every user who uses the card process were able to see then we would have used the 'read' role permission for the card process.

If a table is from another 'module' then it is possible to base a new table on the original one solely for the purpose of adding permissions e.g.

  <Table>
    <ZName>gv_swift_table</ZName>
    <Base>swift_table</Base>
    <RelationshipPermissions>
      <ZName>gv_swift_view_card_detail_m_relp</ZName>
      <DisplayName>Allow view of currencies.</DisplayName>
      <RolePermission>
        <ZName>gv_card_detail_m_rp</ZName>
      </RolePermission>
      <PermissionHolderType>authenticated</PermissionHolderType>
      <FAPList>gv_card</FAPList>
      <readable>true</readable>
      <changeable>false</changeable>
      <deleteable>false</deleteable>
      <secureable>false</secureable>
      <appendable>false</appendable>
    </RelationshipPermissions>
    <RelationshipPermissions>
  </Table>

However, bear in mind that when the table is secured (or resecured) the resecure command will only apply the relationship permissions from one definition of the table e.g.

22-10-2019 15:51:14 - Resecuring Table swift_table using object gv_swift_table, choices where: gv_swift_table, ebex_swift_table, swift_table

If we use our new table then it will also have permissions from the base table, however, if multiple systems do this then (at the time of writing) you will need to select which table is used to create the permissions. See SR: 95587 for progress on a proper solution.

Process (transition) permissions[edit]

Simple transition permission[edit]

To make a transition conditional on a permission add something like the following:

      <Condition class="Permission">
        <ZName>gv_card_topup_pc</ZName>
        <DisplayName>User must have permission to top-up cards</DisplayName>
        <FailMessage>User lacks permission to top-up cards</FailMessage>
        <ClassOptions>
          <CheckFirstFormRecord>true</CheckFirstFormRecord>
          <Permission>changeable</Permission>
          <RolePermission>
            <ZName>gv_card_topup_rp</ZName>
            <DisplayName>Top-Up cards</DisplayName>
            <ShortDescription>Allows top-up of new gift cards.</ShortDescription>
          </RolePermission>
        </ClassOptions>
      </Condition>
      <ShowConditionFailMessage>gv_card_topup_pc</ShowConditionFailMessage>

Update additional table(s) permission[edit]

In the example of a card top-up the user will also need permission to create entries in the balances table (and read the balances table) e.g.

    <RelationshipPermissions>
      <ZName>gv_balance_create_topup_relp</ZName>
      <DisplayName>Allow creation of balances for cards.</DisplayName>
      <RolePermission>
        <ZName>gv_card_topup_rp</ZName>
      </RolePermission>
      <PermissionHolderType>authenticated</PermissionHolderType>
      <FAPList>gv_card</FAPList>
      <readable>true</readable>
      <changeable>true</changeable>
      <deleteable>false</deleteable>
      <secureable>false</secureable>
      <appendable>true</appendable>
    </RelationshipPermissions>

Also notice that it has a FAP list entry to ensure we don't inadvertantly allow the user to view or create balance entries by any other mechanism.

Field Permissions[edit]

Field permissions restrict who can see (and/or edit) fields on a process - important, as soon as you add a field permission to a field it will disappear for all users without that permission.

The following example permissions allow the user to read (and for the second set modify) the top-up limits on a card - they are attached to the cards table:

    <RelationshipPermissions>
      <ZName>gv_card_load_limit_relp</ZName>
      <DisplayName>View Card Load Limits</DisplayName>
      <RolePermission>
        <ZName>gv_card_load_limit_rp</ZName>
        <DisplayName>View Card Load Limits</DisplayName>
        <ShortDescription>View the limits that control how much a top-up can be.</ShortDescription>
      </RolePermission>
      <PermissionHolderType>authenticated</PermissionHolderType>
      <FAPList>gv_card_with_limits</FAPList>
      <readable>true</readable>
      <changeable>false</changeable>
      <deleteable>false</deleteable>
      <secureable>false</secureable>
      <appendable>true</appendable>
      <Field>
        <ZName>gv_ml_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_mxl_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_mb_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_li_per_card</ZName>
      </Field>
    </RelationshipPermissions>
    <RelationshipPermissions>
      <ZName>gv_card_load_limit_m_relp</ZName>
      <DisplayName>Modify Card Load Limits</DisplayName>
      <RolePermission>
        <ZName>gv_card_load_limit_m_rp</ZName>
        <DisplayName>Modify Card Load Limits</DisplayName>
        <ShortDescription>Modify the limits that control how much a top-up can be.</ShortDescription>
      </RolePermission>
      <PermissionHolderType>authenticated</PermissionHolderType>
      <FAPList>gv_card_with_limits</FAPList>
      <readable>true</readable>
      <changeable>true</changeable>
      <deleteable>false</deleteable>
      <secureable>false</secureable>
      <appendable>true</appendable>
      <Field>
        <ZName>gv_ml_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_mxl_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_mb_per_card</ZName>
      </Field>
      <Field>
        <ZName>gv_li_per_card</ZName>
      </Field>
    </RelationshipPermissions>

Its important to note that when you create a new process it and all of its records are 'new' (placeholders internally) until a transition has been successfully run on the process - to set fields on a new/placeholder process the field permissions (if set) must include 'appendable' being true. This also allows XML authors to create fields that can be set on first creating a process but are then read-only for the remainder of its lifecycle.

KNOWN BUG: All fields appear writeable on placeholder records. SR: 95020

Note that field permissions are not yet implemented on filters (SR: 61148) and thus we have to work around this by using cut down versions of filters and then different role permissions - if for example we have hidden the limit fields then we can have two FAP lists, one which allows the cards to be viewed without the limits (gv_card - containing gv_card_process and gv_card_filter) and another with (gv_card_with_limits - containing gv_card_process and gv_card_filter_with_limits) - the 'with limits' filter would have all the fields and then the 'gv_card_filter' (without limits) could be created as follows:

   <Filter>
    <ZName>gv_card_filter</ZName>
    <Base>gv_card_with_limits</Base>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_ml_per_card_reportfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_mxl_per_card_reportfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_mb_per_card_reportfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_li_per_card_reportfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_ml_per_card_searchfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_mxl_per_card_searchfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_mb_per_card_searchfield</RedeclareDelete>
    <RedeclareDelete>gv_t_cards_autoform_autofiltergv_li_per_card_searchfield</RedeclareDelete>
  </Filter>