Skip to content
Kristian Mandrup edited this page Apr 18, 2014 · 5 revisions

A Permit encapsulates a set of authorization rules for a given type of context, scenario, type of user etc. It is a convenient way to group together a set of rules that conceptually/logically "belong together" and apply under the same condition.

A permit can either implement its rules directly or load its rules from JSON or some other rules storage.

A permit takes an AccessRequest object which contains:

  • user object asking to be authorized
  • action being attempted
  • subject being acted upon
  • context under which the whole action takes place

The permit will first match this access request to see if this permit is even relevant for it and should apply its authorization rules. If so, it will try determine if the user is allowed to perform the given action on the subject, by matching the access request on the rules to see if the rules allow or disallow this request.

Building Permits

permitFor optionally take a Permit class argument, such as AdminPermit to further greater reuse by leveraging OOP concepts such as inheritance, module inclusion etc.

Example:

adminBarPermit = permit-for AdminPermit, 'a man walking into the bar', customPermit

The customPermit can be either an Object or a Function.

Registry

Each time a permit is created, it is registered in the PermitRegistry by name. If the permit is not named explicitly, it will be named Permit-0, Permit-1 etc. Only the first permit of a given name is registered.

Any attempt to later store another permit of the same name will cause an error.

You can clear all permits using PermitRegistry.clearAll! and you can clean all permits of rules using PermitRegistry.cleanAll!

Currently the PermitRegistry is implemented as a singleton, but in the future it will be a class to allow for multiple permit registries and then selecting one to be used for a given environment. Suggestions/ideas are always welcome!

Allow

The Permit Allower has the responsibility to determine if the permit allows a given action on a subject (see AccessRequest).

Each permit that matches for the given access context is then resolved to see if the user really can (is allowed) to perform the action on the subject.

class Permit
  permitAllower (accessRequest) ->
    new PermitAllower(@, accessRequest)

  allows: (accessRequest) ->
    permitAllower(accessRequest).allows!

  disallows: (rule) ->
    permitAllower(accessRequest).disallows!

Permit Matcher

The Permit Matcher is used to test if a permit matches for a given access request and should be used for that request.

Using permit-for

A Permit class can be created via the permitFor factory method.

Example:

sexy-permit = permit-for 'a sexy woman',
  // exclude match (Permit does not apply if this match is fulfilled)
  exMatch: (access) ->
    user = access.user
    user.gender is 'female' and user.looks is 'sexy'

  rules:
    manage: ->
      @ucan 'seduce', ['person'], (obj) ->
        obj.gender is 'male'

Note: The third argument (contextual function) is not yet supported, but will be in the near future

Matching DSL

To facilitate creating generic matchers, a set of MatchMaker classes have been defined for each of the keys in the access request. These are UserMatcher, ActionMatcher, SubjectMatcher, ContextMatcher.

The matchers can be used either directly by instantiation or via the matching(access) convenience methods of Permit, which employs the AccessMatcher that in turn contains convenience methods for using all the specific access matchers just mentioned!

The Permit.matching(access) method returns an AccessMatcher instance with convenience methods:

  • user
  • role
  • action
  • subject
  • subjectClazz
  • context

Access user method:

sexy-permit = permitFor 'a sexy woman',
  // only applies if this match is true
  match: (access) ->
    # matches if access intersects with {user: {type: 'sexy'}}
    @matching(access).user(type: 'sexy')

Access action method:

read-permit = permitFor 'a sexy woman',
  match: (access) ->
    # matches if access intersects with {action: 'read'}
    @matching(access).action('seduce')

Access subject method:

read-permit = permitFor 'a sexy woman',
  match: (access) ->
    # matches if access intersects with {subject: 'Human'}
    @matching(access).subject('Human')

Access context method:

read-permit = permitFor 'a sexy woman',
  match: (access) ->
    # matches if access intersects with {area: 'members'}
    @matching(access).context(area: 'members')

Access role method:

sexy-permit = permitFor 'a sexy woman',
  match: (access) ->
    # matches if access intersects with {user: {role: 'sexy'}}
    @matching(access).role('sexy')

Access subjectClazz method (chaining):

sexy-permit = permit-for 'a sexy woman',
  match: (access) ->
    # matches if {subject: sexy-woman} and sexy-woman.constructor is a Woman class
    @matching(access).subjectClazz('Man').user(type: 'sexy')

The matchOn method takes a hash and executes chaining as above:

@matching(access).matchOn(subjectClazz: 'Man', user: {type: 'sexy'}, action: 'read')