Users and Roles

TIBET Users & Roles

wins

  • User, Org, and Role objects with flexible keyring permission model.
  • Automatic update of <body> for permission-driven style changes.
  • Seamless integration with TIBET's Data Server login processing.
  • Standard VCard format for users to organizations and roles.

concepts

We should be clear about one very important thing before we start…

Client-side "permission" features are conveniences intended to help with reuse. **THEY ARE NOT SECURE!** Your server should enforce data and API security.

Data security needs to be enforced as close to the data as possible. Likewise, API security should always be enforced at the API layer. Neither should be left to the client.

To help ensure security, TIBET makes full use of Java Web Token (JWT) security protocols. When you log in to a JWT-enabled server it will send TIBET a token that will be passed to the server with each API call. The TIBET Data Server (TDS) is JWT-enabled and both sends and verifies JWT tokens for API security. If you're using the TDS with logins enabled you get JWT tokens.

Using JWT helps ensure there's a certain level of trust between client and server; however, as powerful as establishing trust is, supporting JWT does nothing to assist us in organizing functional and visual features that need to vary based on user permissions.

As an example, in some of your application scenarios a manager might see fields a normal user might not, they might have different functionality invoked when they select certain actions. Accounting for these changes can often lead to messy, high-maintenance code.

TIBET's User, Org, and Role features give us a clean, maintainable way to associate style and functionality changes in the client with permissions for the current application user.

Users And Roles

When we talk about permissions we tend to immediately focus on the concept of a "user", more accurately the specific login/username used to log in to our application.

In simple systems we might start out assigning permissions directly to specific users. In larger systems we tend to gravitate quickly toward assigning permissions to "roles". We then associate users with one or more roles as a way of assigning permissions to individual user accounts.

In most modern systems a user gets their permissions from the roles they fill.

TIBET provides TP..core.User and TP.core.Role types to support the two key concepts of user and role. As you might expect a user can fill multiple roles and a role can be assigned to multiple users. There are no arbitrary limits on how many roles a particular user might fill.

As powerful as the user/role model is, there's another aspect of a user profile which can provide permissions…the user's organizational affiliations.

Orgs and Units

When discussing whether a particular user can perform certain operations or see certain data we often care what organization they belong to rather than their role.

Many applications provide different data and features to internal users vs. external users. This could potentially be modeled as "roles" but it's a bit of a pollution of the concept of role. The proper modeling of a "works for" relationship is better handled by "org" and "unit".

What's an org vs. a unit? Well, you might work for XYZ Corp, but are you in Sales or Marketing or Engineering? The "org" in this case is XYZ Corp, but the "unit" just as important. In fact, from a TIBET perspective permissions are based on org+unit and are managed at the unit level.

Work with users and permissions long enough and it becomes clear that to fully describe a user we need to consider their orgs, units, and roles…not just their identity or roles.

The thing is, with multiple many-to-many relationships of different kinds, how can we efficiently decide if a user can perform a certain task or see certain data at runtime?

TIBET's answer lies in the concepts of keys and keyrings.

Keys And Keyrings

To manage permission-sensitive functionality TIBET relies on "keys" and "key rings".

A "key" in a TIBET context is any string you like, usually a one to four letter mnemonic for some permission. For example, you might use 'CA' to define permission to 'Create Account'.

The intersection of the current user's keys and the key requirements you've authored for your application determines what the user sees and which operations they can perform.

Let's reiterate the client might use keys to manage the UI or run different functions but the server should restrict who can access data. ALWAYS secure the data at the server.

Keys, as you might expect, are assigned to one or more key rings. This assignment is typically done in a TIBET-specific file found in every project, namely ~app_dat/keyrings.xml. The assignments themselves are simple enough and follow a pattern similar to:

<keyring id="administrator">
    <key id="C" desc="create"/>
    <key id="R" desc="read"/>
    <key id="U" desc="update"/>
    <key id="D" desc="delete"/>
</keyring>

The previous set of entries define four keys which anyone in the role of 'administrator' will receive. You can have as many keys and keyrings as your application requires.

All TIBET TP.core.Resource objects (users, services, etc) leverage a hasAccessKey method which many operations check to confirm user permissions. In addition, the users list of keys are placed on the <body/> tag automatically to help drive style changes via permissions.

User VCards

In TIBET each user login will trigger loading of a standard VCard containing information about their org, unit, and role assignments.

When a user successfully logs in the TIBET Data Server (TDS) will return that user's VCard in response to a request to the tds.vcard.uri target URI (normally /vcard). If the user has no matching VCard one is generated and returned.

When loading VCards, TIBET will automatically load the file defined by the TIBET configuration variable path.app_vcards (~app_dat/vcards.xml by default).

The application vcards.xml file is automatically loaded and bundled with deployment packages so one way to manage your user vcards is to place them all in that file. You can also create user-specific vcards with a naming convention of {username}_vcard.xml and place them in the same ~app_dat directory.

For example, here's a section from a typical VCard which assigns the related user to the role developer, the org Testers Inc. and the unit The Test Group:

<role><text>developer</text></role>
<org><text>Testers Inc.</text></org>
<vcard-ext:x-orgunit>
    <text>The Test Group</text>
</vcard-ext:x-orgunit>

Assigning an org, unit, or role to a user is one of the key things you must do to connect a user to their appropriate permissions.

For simplicity in loading and configuration permissions, naming conventions matter.

When defining the id values in keyrings you are defining the specific role and/or org+unit names which will be associated with users automatically if their VCard uses the same names. If the names don't match you have to write custom Role or Unit types to add keyrings manually.

Real vs. Effective User

Applications which require "superuser" behaviors can often benefit from the Unix concepts of real and effective user.

For example, a superuser account might log in to an application but need to view the application as a typical user does when providing technical support. Similarly a developer account might need to switch between all the views as part of their development process rather than trying to maintain multiple accounts with various role configurations and logging in/out continuously.

TIBET supports the concepts of real and effective user through API on the TP.core.User type (getRealUser, setRealUser, getEffectiveUser, setEffectiveUser, etc).

In addition to tracking the real and effective user within the type this information is automatically pushed to the current UI through the use of TIBET's acl namespace. The acl namespace is a simple addition TIBET makes which gives us an easy way to add information to the UI.

If you look at the body element in a typical TIBET application you will see two attributes: acl:real and acl:effective which contain data for the real and effective user keys.

Permission-based Styling

When a user logs in TIBET populates the body element with acl:real and acl:effective attributes. By default these are set to the same value but API on the TP.core.User type can be used to set a different effective user value.

The attributes in the acl namespace are filled with the current list of keys for the user they represent. This allows you to create style rules which leverage acl|effective or acl|real to determine how the UI should present itself to the current user.

Permission-based Behavior

Permission-based behavior, e.g. logic in the application that differs based on the user's role or unit affiliations, is managed via TIBET's type system. Since role and user data is managed via types you can alter not only a set of keys but the actual methods which are invoked.

When any role/unit-driven resource (service) is asked to perform a task it delegates some of that responsiblity to the current role and unit types. This happens automatically, allowing you to focus on messaging the resource in a common way, while benefiting from permission-specific code.

The essential point is that rather than complicated switch statements or other syntax trying to manage the complexity of what a certain user with certain roles in a certain org can do you can leverage inheritance and messaging from Role and Unit types that are easy to maintain.

cookbook

Adding A User

If you are using the TIBET Data Server (TDS) you can take advantage of simple file-based user-management or integrate with any passport-compatible user authentication system.

NOTE: Using the file-based user management system is NOT SECURE even with the encryption support TIBET provides. DO NOT USE IT FOR PRODUCTION SYSTEMS.

When using the TDS's file-based user authentication you add a user via the TIBET CLI, in particular by using the tibet user command:

$ tibet user
Usage: tibet user <username> [--pass <password>] [--env <env>] [--role <role|roles>] [--org <org|orgs>] [--unit unit]

Although not intended for production use, TIBET's file-based user information is still encrypted. To use the tibet user command (and successfully run a TDS instance that can verify the users you've added) you must ensure the environment includes a value for TIBET_CRYPTO_KEY.

For example, using the tibet user command (or starting the TDS) without an encryption key in place results in messages similar to the following:

$ tibet user foo --pass bar
Error processing user: No secret key for encryption. $ export TIBET_CRYPTO_KEY=""

If you export a value for your crypto key you can then successfully use the command, which will create the user record and generate a default VCard file for the user which includes any org, unit, and role information you provided:

$ export TIBET_CRYPTO_KEY="donotreusethisnotverysecretkey"

$ tibet user foo --pass bar
New user id. Inserting user record.
Generating vcard in /Users/ss/temporary/hello/public/TIBET-INF/dat/foo_vcard.xml
User added.

The user information is stored in the file pointed to by the TIBET Configuration setting for path.user_file (usually users.json in the top of your project).

Here's the small section resulting from our previous tibet user command for the foo user. Notice the encrypted password value:

"foo": {
    "id": "foo",
    "org": [],
    "unit": "guest",
    "role": [],
    "pass": "ba7ddb8eddcf857f31c24bf78517c46b:274f93"
}

Of course, you must use the same key for any subsequent access to that user from either the TIBET CLI or TDS. Don't forget your key or you'll have to recreate all your users.

Removing A User

If you're using the TIBET Data Server's file-based user management (for development or other non-production purposes) and you want to remove a user record simply edit the file referenced by tibet config path.user_file:

$ tibet config path.user_file
~/users.json

If you open ~/users.json and delete the entry for the user in question then save the file you should be good to go. You'll want to restart the TDS to ensure it reads the new file if it's critical that you immediately remove the user's ability to log in to a running server.

Adding A VCard (via tibet user)

The easiest way to add a VCard is to use the tibet user command.

Even if you have no intention of using the TDS's file-based security you can leverage the automatic VCard generation of the tibet user command:

$ export TIBET_CRYPTO_KEY="donotreusethisnotverysecretkey"

$ tibet user foo --pass bar --org 'XYZOrg' --unit 'sales' --role 'flunky'
New user id. Inserting user record.
Generating vcard in /Users/ss/temporary/hello/public/TIBET-INF/dat/foo_vcard.xml
User added.

NOTE that if the user already exists and you want to create new VCard data you'll need to delete the current VCard file or move it to a new location. The tibet user command does not currently support "updating" a VCard.

Adding A VCard (manually)

To add a VCard for a user manually you can copy any existing VCard file and edit it, placing the file in the ~app_dat directory with the proper name.

It is recommended that you manage user-specific VCard data in separate files and application-level (service) VCard data in the ~app_dat/vcards.xml file.

Adding A Keyring

Keyrings are managed in the ~app_dat/keyrings.xml file (the path.app_keyrings file). To add a new keyring add a <keyring> element with a unique id.

To get the keyring to automatically be assigned to users with a particular role make the id value match the role name you are targeting. If you follow this convention TIBET will automatically assign the keys for the keyring to any users which have that role in their VCard entry:

<keyring id="flunky">
</keyring>

Adding A Key

A key is a simple string you arbitrarily assign to a particular permission. Adding a new one is simple, just add a <key> element to the keyrings it should be a part of:

<keyring id="flunky">
    <key id="FUN" desc="permission to have fun"/>
</keyring>

Note that you can also assign keys to a keyring by referencing another keyring:

<keyring id="developer">
    <keyring ref="administrator"/>
    <key id="B" desc="build"/>
</keyring>

When you use the ref attribute on a nested keyring as above all keys for that keyring are implicitly made a part of the enclosing keyring.

Sample Keyring File

<?xml version="1.0"?>
<keyrings xmlns="http://www.technicalpursuit.com/1999/tibet">

    <!--
    A file of sample keyring entries, similar to those you might leverage as
    part of TIBET's TP.core.User type and its org/unit/role permission model.

    When a TP.core.Unit or TP.core.Role type is loaded it will typically assign
    one or more keyrings during the type initialize method. For example, the
    TP.core.Role type automatically assigns the keyring named "guest" as a
    default set of keys all users will acquire.

    NOTE that in these entries we show a few examples of defining a keyring as
    containing one or more keys as well as one or more nested keyrings. This
    hopefully shows how you can avoid repeating blocks of permissions across
    different keyrings by placing common keys into "sub rings" that you assign
    to the more publicly visible keyrings.

    We also show an example of a "precompiled" set of keys, effectively a
    keyring element with a keys attribute containing the keys rather than child
    content. This is a more compact form that TIBET leverages both as a cacheing
    value and as a shorthand format.
    -->

    <!--  role-focused -->

    <keyring id="guest">
        <key id="R" desc="read"/>
    </keyring>

    <keyring id="administrator">
        <key id="C" desc="create"/>
        <key id="R" desc="read"/>
        <key id="U" desc="update"/>
        <key id="D" desc="delete"/>
    </keyring>

    <keyring id="developer">
        <keyring ref="development"/>
        <key id="B" desc="build"/>
    </keyring>

    <keyring id="qualitycontrol">
        <keyring ref="development"/>
    </keyring>

    <keyring id="user">
        <key id="R" desc="read"/>
    </keyring>

    <!--  unit-focused -->

    <keyring id="development">
        <keyring ref="administrator"/>
        <key id="T" desc="test"/>
    </keyring>

    <keyring id="operations">
      <keyring ref="administrator"/>
    </keyring>

    <keyring id="public">
      <key id="R" desc="read"/>
    </keyring>

    <!--
    A "precompiled" keyring, avoiding computation of nested key values. This is
    a good format to use when leveraging a web service to acquire keyrings by ID
    since it avoids space and computation overhead (at the expense of a little
    maintainability).
    -->
    <keyring id="staff" keys="C R U"/>

</keyrings>

Sample VCard

<vcard xmlns="urn:ietf:params:xml:ns:vcard-4.0" xmlns:vcard-ext="http://www.technicalpursuit.com/vcard-ext">
    <fn><text>dev@technicalpursuit.com</text></fn>
    <n>
      <surname></surname>
      <given></given>
      <prefix></prefix>
    </n>
    <nickname><text>dev@technicalpursuit.com</text></nickname>
    <role><text>developer</text></role>
    <org><text>Technical Pursuit Inc.</text></org>
    <tel>
      <parameters>
        <type>
          <text>work</text>
          <text>voice</text>
        </type>
      </parameters>
      <uri></uri>
    </tel>
    <email>
      <text>dev@example.com</text>
    </email>
    <url>
      <parameters>
        <type>
          <text>work</text>
        </type>
      </parameters>
      <uri></uri>
    </url>
    <tz><text></text></tz>
    <vcard-ext:x-orgunit>
        <text>staff</text>
    </vcard-ext:x-orgunit>
</vcard>

code

User, Role, and related types are in ~lib/src/tibet/kernel/TIBETWorkflowTypes.js.

TIBET's TP.tibet.keyring and TP.ietf.vcard types are in ~lib/src/tibet/kernel/TIBETWorkflowDOMTypes.js.

Project vcard and keyring template files are found in the ~app_dat/ directory.

Sample tests are in ~lib/test/src/tibet/permissions and provide some additional sample code for leveraging keyrings, vcards, etc.