2017-01-07 19 views
7

Haskell'de basit bir hiyerarşik erişim kontrol sistemini nasıl tanımlayabilirim?Yazılı Hiyerarşik Erişim Kontrol Sistemi

Görevlerim Public > Contributor > Owner, bu roller bir hiyerarşi içerisindedir. Public tarafından yapılabilecek her şey Contributor ve Owner; ve bunun gibi.

Benzer şekilde, işlemler hiyerarşide de bulunur: None > View > Edit. Bir Rolün Düzenlenmesine izin verilirse, ayrıca Görüntüleyebilmelidir.

publicEditable :: Policy 
publicEditable = Policy $ const Edit 

Ama tür sistemi bunun gibi aptalca politikaları (Edit için Public izin ancak herhangi erişimini engeller olduğunu tanımlayan beni engellemez: ben kamu düzenlenebilir politikasını ifade edebilir Bu sistemde

data Role = Public | Contributor | Owner 
data Operation = None | View | Edit 

newtype Policy = Policy (Role -> Operation) 

Owner): Ben tip sistemde Rolü ve Çalışma hiyerarşik doğasını ifade edebiliriz nasıl

stupidPolicy :: Policy 
stupidPolicy = Policy check where 
    check Public  = Edit 
    check Contributor = View 
    check Owner  = None 

?

cevap

7

Policy 'un yapıcılarına erişimi olan herkes, Policy numaralı parçayı ayrı bir yere alabilir ve muhtemelen bir araya getirerek bir araya getirebilir. Bu modülün dışındaki Policy yapıcısını gösterme. Bunun yerine, iyi biçimlendirilmiş olması garantili ve Monoid arabirimini invaryanları kırmadan bunları oluşturmak için ortaya çıkan ilkeleri oluşturmak için smart constructor sağlayın. Policy tip özetinin tutulması, parasal olmayan politikalarla sonuçlanabilecek tüm kodların bu modülde tutulmasını sağlar. bir Monoid örneğine bir Ord örneği döner the monoid for functions, fonksiyon tuşu aracılığıyla nokta-bazlı bir Monoid kaldırır ve the Max newtype:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

module Policy (
    Role(..), 
    Level(..), 
    Policy, -- keep Policy abstract by not exposing the constructor 
    can 
    ) where 

import Data.Semigroup (Semigroup, Max(..)) 

data Role = Public | Contributor | Owner 
    deriving (Eq, Ord, Bounded, Enum, Show, Read) 
data Level = None | View | Edit 
    deriving (Eq, Ord, Bounded, Enum, Show, Read) 

I base den Monoid örnekleri bir çift ödünç GeneralizedNewtypeDeriving kullanıyorum Aşağıda her zaman mappend'un argümanlarını daha büyük seçerek. politikaları oluştururken

Yani Policy bireyin Monoid örneği otomatik Level sıralamasını yönetecek: Belirli bir role çelişen seviyeleri ile iki politikaları oluştururken her zaman daha hoşgörülü birini seçeceğim. Bu,'u ek işlemini yapar: ilkeleri, hiç kimseye izin vermeyen "varsayılan" ilke olan mempty'a ekleyerek tanımlayabilirsiniz.

newtype Policy = Policy (Role -> Max Level) deriving (Semigroup, Monoid) 

grantRole ve Level sıralamasının özelliklerini saygı ilkeleri üreten bir akıllı yapıcı olup. Bir role izin vermenin de daha fazla ayrıcalıklı rollere izin verdiğinden emin olmak için >= ile rolleri karşılaştırdığımı unutmayın.

grant :: Role -> Level -> Policy 
grant r l = Policy (Max . pol) 
    where pol r' 
      | r' >= r = l 
      | otherwise = None 

can bir politika verilen bir role verilen bir erişim seviyesi verir olmadığını belirten bir gözlem olduğunu.Bir kez daha fazla izin veren düzeylerin daha az izin verici olanları içerdiğinden emin olmak için >= kullanıyorum.

can :: Role -> Level -> Policy -> Bool 
(r `can` l) (Policy f) = getMax (f r) >= l 

Bu modülün ne kadar küçük bir kod aldığına hoş bir şekilde şaşırdım! deriving mekanizmasına, özellikle de GeneralizedNewtypeDeriving mekanizmasına yaslanmak, "sıkıcı" kodun sorumlularını koymak için gerçekten güzel bir yöntemdir, böylece önemli şeylere odaklanabilirsiniz. Bu politikaların


Kullanımı şuna benzer:

module Client where 

import Data.Monoid ((<>)) 
import Policy 

Basit olanlar dışında karmaşık politikaları oluşturmak için Monoid sınıfını kullanabilirsiniz.

ownerEdit, contributorView, myPolicy :: Policy 

ownerEdit = grant Owner Edit 
contributorView = grant Contributor View 
myPolicy = ownerEdit <> contributorView 

Ve politikaları test etmek can işlevini kullanabilirsiniz. Örneğin

canPublicView :: Policy -> Bool 
canPublicView = Public `can` View 

:

ghci> canPublicView myPolicy 
False 
+0

Max a' bir Monoid ve olduğu 'Çünkü GHC Policy'' için Monoid örneğini türetmek yapabiliyor doğru olduğunu Am I 'x -> Monoid y' Monoid'dir. Kendi örneğimi de alabilirim: '' (Politika a) 'mappend' (Politika b) = Politika $ \ r -> max (ar) (br)' '' – homam

+0

Evet, GHC tam olarak aynı şekilde üretecektir kod, bu yüzden neden yazmayı bother? –

+0

Bu çok zarif bir çözümdür. Teşekkürler! – homam

3

Benjamin Hodgson çözüm daha basit ve daha şık, ama burada singletons paketin makine kullanarak, bir tür düzeyinde programlama çözümüdür.

fikri politikaları Role ler ve Operation s hem listeyi azalmayan olmalı (Role, Operation) küpe, tipi düzeyinde listeleri olarak temsil edilir olmasıdır. Bu şekilde, [(Public,Edit),(Owner,View)] saçma bir saçmalık elde edemeyiz.

Bazı gerekli uzantıları ve ithalat:

{-# language PolyKinds #-} 
{-# language DataKinds #-} 
{-# language TypeFamilies #-} 
{-# language GADTs #-} 
{-# language TypeOperators #-} 
{-# language UndecidableInstances #-} 
{-# language FlexibleInstances #-} 
{-# language ScopedTypeVariables #-} 
{-# language TemplateHaskell #-} 

import Data.Singletons 
import Data.Singletons.TH 
import Data.Promotion.Prelude (Unzip) 

Biz veri türlerini beyan ve kullanarak bunları singletonize Şablon Haskell:

data Role = Public | Contributor | Owner deriving (Show,Eq,Ord) 
data Operation = None | View | Edit deriving (Show,Eq,Ord) 
$(genSingletons  [''Role,''Operation]) 
$(promoteEqInstances [''Role,''Operation]) 
$(promoteOrdInstances [''Role,''Operation]) 

azalmayan unsurlarla listeler için A sınıfı:

class Monotone (xs :: [k]) 
instance Monotone '[] 
instance Monotone (x ': '[]) 
instance ((x :<= y) ~ True, Monotone (y ': xs)) => Monotone (x ': y ': xs) 

Tip düzeyi listesi olarak belirtilen bir politika verildiğinde, politika işlevini geri döndürün:

policy :: forall (xs :: [(Role, Operation)]) rs os. 
      (Unzip xs ~ '(rs,os), Monotone rs, Monotone os) 
     => Sing xs 
     -> Role 
     -> Operation 
policy singleton role = 
    let decreasing = reverse (fromSing singleton) 
     allowed = dropWhile (\(role',_) -> role' > role) decreasing 
    in case allowed of 
     [] -> None 
     (_,perm) : _ -> perm 

Testler GHCi içinde:

ghci> :set -XDataKinds -XPolyKinds -XTypeApplications 
ghci> policy (sing::Sing '[ '(Public,View),'(Owner,Edit) ]) Owner 
Edit 
ghci> policy (sing::Sing '[ '(Public,Edit),'(Owner,View) ]) Owner 
*unhelpful type error* 
+0

mükemmel karşılaştırma. hala statik/dinamik tasarım alanı ile hokkabazlık – nicolas