Hello.

I've been playing around trying to write a framework to support/enforce access 
control to resources.  So far my efforts have yielded little but bruised 
forehead and compressed plaster-board.

What I'd like is a solution that:
  (1) prevents access to resources except via a fine-grained permissions 
checking gateway

  (2) supports on-the-fly permissions eg Bob can see Fred's salary

  (3) supports dynamic role constraints eg Bob can't be both appointor and 
appointee of secret agent status

  (4) allows lack of permission to optionally act as a filter rather than 
cause an abort, eg Bob viewing all salaries returns Fred's but doesn't return 
Tom's rather than aborting altogether because Bob lacks the permission over 
Tom

  (5) well defined behaviour when checking permissions for actions that change 
permissions

  (6) it must be pure, no need for IO.

  (7) ideally required permissions would appear (and accumulate) in type 
signatures via inference so application code knows which are required and 
type checker can reject static/dynamic role constraint violations 


I've attempted a solution using arrows based loosely upon ideas in [1] and 
[2].  I can't figure out how to make it work, even if I did get it working I 
now suspect it can be done far more simply, and I don't think it could ever 
provide feature (7).  For what it's worth it's attached.


Ideally you kind folk could help me come up with a type-level solution to 
satisfy (7), so you could have something like:
deptAveSal :: (HasPerms subject? Read Salary [person]?, HasPerm subject? Read 
Employees dept?, HasRole subject? Manager dept?) => Department -> Salary

Failing that how to do it in a more simple fashion?  I now think that a 
State-like monad exposing only functions taking values in a wrapper type that 
carries required permissions may be sufficient, but still probably couldn't 
satisfy (7).

Failing that my existing attempt has me stumped for a few reasons:
  how do I get hold of the subject and resource so I can build the correct 
permission in Test?  eg the Person whose Salary is needed in salary, and 
who's trying to get it

  where do I get the System from in Test?  eg fakeSystem in personByName

  how to implement the filter functionality in RBAC?  Parametric over 
container types?

I think that perhaps the Validator would need to be a monad that holds the 
initial state of the System to provide a stable set of permissions/roles and 
that the subject and System should also be threaded through the arrows for 
use/modification.


Any help you can offer for my aching cranium will be _much_ appreciated.
Thanks.


[1] Encoding Information Flow in Haskell - Peng Li, Steve Zdancewic. 
http://www.seas.upenn.edu/~lipeng/homepage/flowarrow.html
[2] A Library for Secure Multi-threaded Information Flow in Haskell - 
Alejandro Russo, Tsa-chung Tsai, John Hughes. 
http://www.cs.chalmers.se/~russo/publications.html
{-# LANGUAGE
  FunctionalDependencies,
  MultiParamTypeClasses
  #-}

module RBAC (
  Arrow (),
  Validator (..),
  apply,
  require,
  applyRequire,
  check
  ) where

import           Control.Arrow (
                    first,
                    pure,
                    (>>>)
                    )
import qualified Control.Arrow as A
import           Data.Set (
                    Set
                    )
import qualified Data.Set as S (
                    empty,
                    fromList,
                    singleton,
                    union
                    )


require :: (A.Arrow a) => p -> Arrow v p a b b
require p = Arr { computation = pure id
                , permissions = S.singleton p
                }

apply :: (A.Arrow a, Ord p) => (b -> c) -> Arrow v p a b c
apply = pure

applyRequire :: (A.Arrow a, Ord p) => (b -> c) -> p -> Arrow v p a b c
applyRequire f p = apply f >>> require p

-- filter and filter' use permissionHeld... perhaps permissionHeld is
-- implemented by knot-tying the set of permissions that get checked
-- in the process of executing the arrow back in as an input.  Perhaps
-- there should be permissions and filterPermissions in the arrow so
-- only the filterPermissions are passed back in.
--filter :: (A.Arrow a) => p -> Arrow v p a b (Maybe b)
--filter p = Arr { computation = pure if permissionHeld p 
--                                      then Just 
--                                      else const Nothing
--               , permissions = S.singleton p
--               }

-- Filters out each element of the list for which the constructed
-- permission isn't held.  Would be nice if this could be generalised
-- to any container, not just lists.
--filter' :: (A.Arrow a) => (b -> p) -> Arrow v p a [b] [b]
--filter' pf = error "filter': not implemented" 

data Arrow v p a b c = Arr { computation :: a b c
                           , permissions :: Set p
                           }

instance (Ord p, A.Arrow a) => A.Arrow (Arrow v p a) where
  pure f = Arr { computation = pure f
               , permissions = S.empty
               }
  (Arr c1 ps1) >>> (Arr c2 ps2) = Arr { computation = c1 >>> c2
                                      , permissions = S.union ps1 ps2
                                      }
  first (Arr c ps) = Arr { computation = first c
                         , permissions = ps
                         }

type Checked a b = Either a b

class (Ord p) => Validator v p e | v -> p, v -> e where
  validate :: v -> Set p -> Checked e ()

check :: (A.Arrow a, Validator v p e) =>
          v -> Arrow v p a b c -> a b (Checked e c)
check v (Arr c ps) = either (pure . const . Left) (const (c >>> pure Right))
                      (validate v ps)

{-# LANGUAGE
  MultiParamTypeClasses,
  TypeSynonymInstances
  #-}
module Test (
    System (),
    Person (),

    name,
    personByName,
    salary,
    manager
  ) where

import           Control.Arrow
import           Data.Maybe
import qualified RBAC as RBAC

data System = Sys [Person]

data Person = Person { pName            :: Name
                     , pSalary          :: Salary
                     , pManager         :: Name
                     , pSecretAgent     :: Bool
                     , pCanAppointAgent :: Bool
                     }

type Name = String
type Salary = String

type Protected a b = RBAC.Arrow Validator Permission (->) a b
 
type Permission = String

type DynamicConstraintViolation = String

data Validator = Validator

instance RBAC.Validator Validator Permission DynamicConstraintViolation where
  validate = error "validate not implemented"

findPerson (Sys ps) n = (lookup n . map (\p -> (pName p, p))) ps

name :: Protected a Person -> Protected a Name
name = (>>^ pName)

personByName :: Protected a Name -> Protected a (Maybe Person)
personByName = ar (findPerson fakeSystem) (fakeP "perm: s observe p")

salary :: Protected a Person -> Protected a Salary
salary = ar pSalary (fakeP "perm s read p.salary")

manager :: Protected a Person -> Protected a Person
manager = (>>^ fromJust) . personByName . 
              ar pManager (fakeP "perm s read p.manager")

ar f p = (>>> RBAC.applyRequire f p)

fakeP :: String -> Permission
fakeP = error

fakeSystem :: System
fakeSystem = error "where does the Sys come from?"

protect :: a -> Protected () a
protect x = pure (\() -> x)

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to