claims
LeisureLink's object model and utilities for querying security claims in nodejs.
Claims Based Security
In the context of LeisureLink's federated security, the term Claim
refers to a statement that a security principal
makes about itself or another security principal
. Every user of the system, whether a human user or a system user, is authenticated as a security principal
, and access-control, where applicable, relies upon a principal's claims.
Security Principals
Presently, there are two types of security principals; user principals
and endpoint principals
.
User principals represent the human users that log in to our websites, applications, and APIs and otherwise use our services.
Endpoint principals represent system users. Each of our internal API's, websites, hubs, applications, CLIs, as well as external systems that connect with us are represented in the security system as endpoint principals.
Claimsets
Within the security system, claims are organized into claimsets. A claimset is a collection of claims defined in a claimset specification
and administered by a claims commissioner
.
Claimsets and their constutuent claims are identified by the claimset's identfier (csid
).
Claims
Claims
are trusted, system-wide data points attributed to a principal. Logically, they are similar to key-value pairs; they have two parts:
-
clid
- the claim's unique identifier -
value
- the claim's value
clid
(Claim Identifiers)
LeisureLink's federated security is a distributed authority system. This means that there is not a single service that catalogues the entirety of a principal's claims. Instead, various services within the operating environment perform
the role of claims commissioner
and proffer subsets of a principal's claims to the system. This scheme enables seperation of concern in the security system; e.g. the pmc-account-api
is responsible for associating principals
with PMCs, and establishing a principal's permissions for conducting operations against rental properties. One of the mechanisms that enables the system to understand the separation of security concerns is the way a clid
is constructed.
Claims identifiers are encoded similar to JSON Pointer fragement identifiers. This encoding enables us to namespace the security related data points and enables the
security system to lookup the claim commissioner
and otherwise trust the veracity of claims.
It is important to note that claim identfiers may be templated. Templated ids are frequently used to represent the relationship between a principal and another generated id within the system. For example, a PMC (property management company) may be represented by a specific entity within the system. The specification for a user's relationship with a PMC would then use a templated claim in order indicate that a claim is valid for a variety of different PMC identfiers.
Examples
The fragment identifier #/pmc/adm
is a valid clid
. The first segment of the clid
indicates which claims commissioner
is the authority over the claim. This first segment is referred to as the csid
(Claimset Identifier). Additional segments in a clid
denote the claim's scope and meaning to the corresponding commissioner.
Claim Types
The system understands 3 types of claims:
fact
- a claim that bears a corresponding valuerole
- a claim that denotes membershippermissions
- a claim composed of permission flags
Fact Claim
A fact claim is a data point related to the principal that the system asserts to be factual, some of these are principalId
, first-name
, last-name
, email-address
, etc.
Facts always have a corresponding text value. Take for example the following claim specification
for the principal's email address:
...
{
"clid": "#/sys/em",
"kind": "fact",
"name": "Email address"
}
Javascript code using a principal's email address might be:
// ... assuming systemId is a principal's systemId...
Claims.forPrincipal(systemId)
.get('#/sys/em', function (err, claim) {
if (!err && 'me@my.com' === claim) {
console.log('Yay, its me!');
}
});
Role Claim
A role claim denotes that the principal has been granted membership in the identified role.
Roles always have a boolean value of true
or false
, indicating whether the principal is a member. Take for example the following claim specification
for the PMC administrator:
...
{
"clid": "#/pmc/adm",
"kind": "role",
"name": "PMC Administrator"
}
Javascript code verifying a principal's 'PMC Administrator' membership:
// ... assuming systemId is a principal's systemId...
Claims.forPrincipal(systemId)
.get('#/pmc/adm', function (err, claim) {
if (claim) {
console.log("Yay, I'm an admin!");
}
});
Role claims may frequently be specified with template parameters, given the above example we may create a claim specification for a specific PMC's administrator using the following JSON:
...
{
"clid": "#/pmc/{pmcId}/adm",
"kind": "role",
"name": "PMC Administrator"
}
...
Verifying the presence of this claim can be done in a couple ways. The most straightforward way to query for a templated claim is done when we want to query if the principal is a related to a specific id.
// ... assuming systemId is a principal's systemId...
Claims.forPrincipal(systemId)
.get('#/pmc/12/adm', function (err, claim) {
if (claim) {
console.log("Yay, I'm an admin!");
}
});
Alternatively, we may want to know if the principal is just an Administrator of some PMC rather than any specific id, this may be accomplished with the following JS:
// ... assuming systemId is a principal's systemId...
Claims.forPrincipal(systemId)
.get('#/pmc/{pmcId}/adm', function (err, claim) {
if (claim) {
console.log("Yay, I'm an admin!");
}
});
Permissions Claim
A permissions claim is composed of a set of permissions that have been granted to a principal.
Permissions have a value that is the set of permissions defined in the claim's specification that have been granted a principal. Take for example the following claim specification
for a PMC's rental unit's data permissions:
...
{
"clid": "#/pmc/{pmcId}/units/[]",
"kind": "permissions",
"name": "PMC Rental Unit Permissions",
"permissions": [{
"flag": "c",
"description": "Permission to create the PMC's unit data."
}, {
"flag": "r",
"description": "Permission to read the PMC's unit data."
}, {
"flag": "u",
"description": "Permission to update the PMC's unit data."
}, {
"flag": "d",
"description": "Permission to delete the PMC's unit data."
}],
"parameters": [{
"name": "pmcId",
"position": 1,
"type": "string"
}]
Each discreet permission is defined in the claim's specification. Javascript code testing whether a principal has permission to read and update rental units might be:
// ... assuming systemId is a principal's systemId...
Claims.forPrincipal(systemId)
.get('#/pmc/123/units/[ru]', function (err, claim) {
if (!err && claim === '[ru]') {
console.log("Whew, I can read and update PMC 123's rental units!");
}
});
Object Model
The object model implements the business logic necessary to pull claim-specs and claims close to where security demands are being made. It embodies logic that implements select-through caching at many levels in order to reduce unnecessary network requests. The caching adheres to time-to-live declarations made in the claimset specifications
.
claims
defines the following objects:
-
Claim
- base Claim class-
FactClaim
- a specialized Claim class for facts -
PermissionsClaim
- a specialized Claim class for permissions -
RoleClaim
- a specialized Claim class for roles
-
-
ClaimsetSpec
- encapsulates a claimset's specification -
ClaimSpec
- encapsulates a claim's specification -
PrincipalClaims
- a short-lived, principal-specific context for querying claims and making security demands. -
PrincipalClaimset
- a short-lived, principal-specific utility class that aggregates and caches the results of claim queries.
Claims Class
The Claims
class is the entrypoint for client code working with claims.
-
#forPrincipal
- factory method; creates a principal specific claims context (PrincipalClaims
) for the principal specified by systemId -
#getClaimetSpecs
- gets theclaimset specification
corresponding to the specifiedcsid
and constructs aClaimsetSpec
-
#getClaimSpecs
- gets theclaim specification
corresponding to the specifiedclid
and constructs aClaimSpec
The Claims
class' constructor requires that three resolver functions be specified, one that resolves claimsets and two others that resolves a principal's claims:
// assuming the two resolvers are already defined in the runspace:
var claims = new Claims({
claimsetSpecResolver: getClaimsetSpecs,
claimResolver: getClaim,
claimsResolver: getClaims
});
The Claims
class intentionally does not care how these functions are implemented; it only cares about the functions' contract as specified below.
Required Claimset Specification Resolver
A claimset specification resolver retrieves a specified claimset
. It is a function that takes two parameters:
-
csid
- the claimset's identifier -
callback
- a nodejs style callback function where results are returned
function getClaimsetSpecById( csid, callback ) {
//... implementation elided.
}
During normal operation, the resolver must not throw an exception. Error conditions may be communicated back to the caller via the callback's first argument.
The resolver's success response must be a javascript object constructed from the requested claimset's specification. Since claimsets are specified in JSON Schema format, the direct result of JSON.parse
is what is expected as the top-level success result provided as the callback's second argument.
Required Claim Resolvers
A claim resolver retrieves a principal's claims.
-
clid
- the claim's identifier (or claims' identifiers). Please note that this method should support being called with an array. -
systemId
- the principal'ssystemId
-
callback
- a nodejs style callback function where results are returned
function getClaim( clid, systemId, callback ) {
//... implementation elided
// _N.B._ clid may be a single value _or_ an array.
}
During normal operation, the resolver must not throw an exception. Error conditions may be communicated back to the caller via the callback's first argument.
The resolver's success response must be the claim's value, according the the claim's type, and returned as the callback's second argument.