Skip to content

Latest commit

 

History

History
428 lines (317 loc) · 10.8 KB

README.md

File metadata and controls

428 lines (317 loc) · 10.8 KB

Tiny RBAC

Clojars Project

Small role based access control library for clojure

Table of Contents

Features

Builder

With the builder you can define role set via

  • single map
(b/init {:tiny-rbac.core/resources :post
         :tiny-rbac.core/actions   {:post [:read :write]}
         :tiny-rbac.core/inherits  {:poster :reader}
         :tiny-rbac.core/roles     {:reader {:post {:read [:own :friend]}}
                                    :poster {:post {:write :own}}}})
  • non namespaced map
(b/init {:resources :post
         :actions   {:post [:read :write]}
         :inherits  {:poster :reader}
         :roles     {:reader {:post {:read [:own :friend]}}
                     :poster {:post {:write :own}}}})
  • multiple maps
(-> (b/init {:tiny-rbac.core/resources :post})
    (b/init {:tiny-rbac.core/actions {:post [:read :write]}})
    (b/init {:tiny-rbac.core/roles {:reader {:post {:read #{:own :friend}}}}})
    (b/init {:tiny-rbac.core/roles {:poster {:post {:write #{:own}}}}})
    (b/init {:tiny-rbac.core/inherits {:poster :reader}}))
  • code
(-> (b/add-resource {} :post)
    (b/add-action :post [:read :write])
    (b/add-role :reader)
    (b/add-role :poster)
    (b/add-permission :reader :post :read [:own :friend])
    (b/add-permission :poster :post :write :own)
    (b/add-inheritance :poster :reader))

All examples above providing the same result:

(def role-set
  {:tiny-rbac.core/resources #{:post},
   :tiny-rbac.core/actions   {:post #{:read :write}},
   :tiny-rbac.core/inherits  {:poster #{:reader}}
   :tiny-rbac.core/roles     {:reader {:post {:read #{:own :friend}}}
                              :poster {:post {:write #{:own}}}}})

Core

With the core you can query for

  • resource(s)
(is (= #{:post}
       (c/resources {...})))

(is (= :post
       (c/resource {...} :post)))
  • action(s)
(is (= #{:read :write}
       (c/actions {...} :post)))

(is (= :read
       (c/action {...} :post :read)))
  • role(s)
(is (= #{:reader :poster}
       (c/roles {...})))

(is (= :reader
       (c/role {...} :reader)))
  • inheritance
(is (= #{:reader}
       (c/inherit {...} :poster)))

(is (= #{}
       (c/inherit {...} :reader)))
  • permission(s)
(is (= #{:own :friend}
       (c/permissions {...} :poster :post :read)))

(is (= :own
       (c/permission {...} :poster :post :read :own)))

(is (= true?
       (c/has-permission {...} :poster :post :write)))

(is (= false?
       (c/has-permission {...} :reader :post :write)))

Setup

Add

Clojars Project

to dependencies

Usage

Builder

Creating a role-set via functions

(:require
  [tiny-rbac.builder :as b])

(def role-set
  (->
    ;; empty role-set
    {}

    ;; defining a new resource in the given role-set
    (b/add-resource :post)

    ;; defining actions for a resource
    ;; Throws an exception if the given resource not defined
    (b/add-action :post [:read :write])

    ;; add an action to the resource
    ;; Throws an exception if the given resource not defined
    (b/add-action :post :tag)

    ;; defines roles into the given role-set
    (b/add-role [:reader :guest])

    ;; adds role to the given role-set
    (b/add-role :poster)

    ;; creates :admin role which inherits its roles from :poster
    ;; Throws an exception if inherited role doesn't exists,
    ;; or in case of cyclic inheritance
    (b/add-inheritance :admin :poster)

    ;; Provides permission for :reader role, to :read :own and :friend's :posts
    ;; Throws an exception if
    ;;  :post resource not defined 
    ;;  :post resource has no :read action
    ;;  :reader role doesn't exists 
    (b/add-permission :reader :post :read [:own :friend])

    ;; Provides permission for :poster role, to :write :own :posts
    ;; Throws an exception if
    ;;  :post resource not defined 
    ;;  :post resource has no :write action
    ;;  :poster role doesn't exists 
    (b/add-permission :poster :post :write :own)

    ;; Adds :reader as inheritance to :poster
    ;; Throws an exception if inherited :reader role doesn't exists,
    ;; or in case of cyclic inheritance
    (b/add-inheritance :poster :reader)))

Every add-* functions are supporting bulk and single addition.

(is (= (-> ...
           (b/add-action ... :read)
           (b/add-action ... :write))
       (b/add-action ... ... [:read :write])))

Creating a role-set from maps

Maps can be also used to create role-set. It's highly advised to use init function to convert / validate a pure clojure map to role-set.

See Features for details.

Tightening the role-set

(:require
  [tiny-rbac.builder :as b])

(def role-set ...)

;; Remove resource(s), with all referring actions and permissions from role-set
;; Throws an Exception if the resource does not exists 
(b/delete-resource role-set :comment)
(b/delete-resource role-set [:post :comment])

;; Remove action(s) from role-set's resource, with all permissions on it
;; Throws an Exception if the resource or the action does not exists 
(b/delete-action role-set :resource :comment)
(b/delete-action role-set :resource [:post :comment])

;; Deleting role(s)
;; Removes role(s) from all inheritances
;; Throws an Exception if the role is missing
(b/delete-role role-set :lurker)
(b/delete-role role-set [:guest :lurker])

;; Removing inheritance(s) from role
;; Throws an Exception if the role not inherited from given one
(b/delete-inheritance role-set :admin :lurker)
(b/delete-inheritance role-set :admin [:guest :lurker])

;; Revoking permission(s) from role
;; Throws an Exception if resource, action, role or permission is missing
(b/delete-permission role-set :role :resource :action :permission)
(b/delete-permission role-set :role :resource :action [:permission-1 :permission-2])

Calling delete-* functions with ::b/all removes all referred instances. Like:

(delete-resource role-set :b/all)

results in role-set without resources, actions, or any permissions

Core

The core functions are for resolving resources, actions, roles and permissions from already built role-set. These functions are not providing any validations, all validations are done in the build steps. When a set is missing from role-set, functions are returning empty sets.

Resources

(:require
  [clojure.test :refer [is]]
  [tiny-rbac.core :as c])

(def role-set
  {:tiny-rbac.core/resources #{:post},
   :tiny-rbac.core/actions   {:post #{:read :write}},
   :tiny-rbac.core/inherits  {:poster #{:reader}}
   :tiny-rbac.core/roles     {:reader {:post {:read #{:own :friend}}}
                              :poster {:post {:write #{:own}}}}})

;; Get all resources from role-set
(is (= #{:post}
       (c/resources role-set)))

;; Returns empty set when role-set has no resources
(is (= #{}
       (c/resources {})))

;; Get a resource from role-set
(is (= :post
       (c/resource role-set :post)))

;; Returns nil when a resource is missing
(is (nil?
      (c/resource role-set :comment)))

Actions

(:require
  [clojure.test :refer [is]]
  [tiny-rbac.core :as c])

(def role-set
  {:tiny-rbac.core/resources #{:post},
   :tiny-rbac.core/actions   {:post #{:read :write}},
   :tiny-rbac.core/inherits  {:poster #{:reader}}
   :tiny-rbac.core/roles     {:reader {:post {:read #{:own :friend}}}
                              :poster {:post {:write #{:own}}}}})

;; Get all actions for resource from role-set
(is (= #{:read :write}
       (c/actions role-set :post)))

;; Returns empty set when role-set has no action for resource
(is (= #{}
       (c/actions {} :post)))

;; Get an action for resource from role-set
(is (= :read
       (c/action role-set :post :read)))

;; Returns nil when the action is missing
(is (nil?
      (c/action role-set :post :tag)))

;; Returns nil when the resource is missing
(is (nil?
      (c/action role-set :comment :read)))

Roles

(:require
  [clojure.test :refer [is]]
  [tiny-rbac.core :as c])

(def role-set
  {:tiny-rbac.core/resources #{:post},
   :tiny-rbac.core/actions   {:post #{:read :write}},
   :tiny-rbac.core/inherits  {:poster #{:reader}}
   :tiny-rbac.core/roles     {:reader {:post {:read #{:own :friend}}}
                              :poster {:post {:write #{:own}}}}})

;; Get all roles from role-set
(is (= {:guest  {}
        :reader {:post {:read #{:own :friend}}}
        :poster {:post {:write #{:own}}}}
       (c/roles role-set)))

;; Returns empty set when role-set has no roles
(is (= #{}
       (c/roles {})))

;; Get a role from role-set
(is (= {:permits {:post {:read #{:own :friend}}}}
       (c/role role-set :reader)))

;; Returns nil when the action is missing
(is (nil?
      (c/role role-set :admin)))

Permissions

(:require
  [clojure.test :refer [is]]
  [tiny-rbac.core :as c])

(def role-set
  {:tiny-rbac.core/resources #{:post},
   :tiny-rbac.core/actions   {:post #{:read :write}},
   :tiny-rbac.core/inherits  {:poster #{:reader}}
   :tiny-rbac.core/roles     {:reader {:post {:read #{:own :friend}}}
                              :poster {:post {:write #{:own}}}}})

;; Get all permissions for resource and action
(is (= #{:own :friend}
       (c/permissions role-set :reader :post :read)))

;; returns empty set if no permissions for given action
(is (= #{}
       (c/permissions role-set :reader :post :write)))

;; Check actual permission for resource and action
(is (= :own
       (c/permission role-set :reader :post :read :own)))

;; Returns nil when the actual permission is missing
(is (nil?
      (c/permission role-set :reader :post :read :all)))

;; Has the inherited permissions
(is (= #{:own :friend}
       (c/permissions role-set :poster :post :read)))

;;Has any kind of permission to do an action on resource
(is (true?
      (c/has-permission role-set :poster :post :read)))

;;Doesn't have any kind of permission to do an action on resource
(is (false?
      (c/has-permission role-set :reader :post :write)))

Project Status

  • complete

Room for Improvement

To do:

  • Release to clojars
  • Examples and use cases would be nice to provide

Acknowledgements

Many thanks to Flexiana for all the support.

Contact

Created by @g-krisztian - feel free to contact me!