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.
Contents
Concepts
Cookbook
- Adding A User
- Removing A User
- Adding A VCard (via
tibet user
) - Adding A VCard (manually)
- Adding A Keyring
- Adding A Key
- Sample Keyring
- Sample VCard
Code
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="{{secret}}"
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 caching 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@coderats.llc</text></fn>
<n>
<surname></surname>
<given></given>
<prefix></prefix>
</n>
<nickname><text>dev@coderats.llc</text></nickname>
<role><text>developer</text></role>
<org><text>CodeRats LLC</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.