2017-01-07 2 views
7

मैं हास्केल में एक साधारण पदानुक्रमित पहुंच नियंत्रण प्रणाली को कैसे परिभाषित कर सकता हूं?टाइपेड पदानुक्रमित एक्सेस कंट्रोल सिस्टम

मेरी भूमिका Public > Contributor > Owner है, ये भूमिकाएं पदानुक्रम में हैं। Public द्वारा किया जा सकता है कि सब कुछ Contributor और Owner द्वारा किया जा सकता है; और इसी तरह।

इसी प्रकार संचालन भी पदानुक्रम में हैं: None > View > Edit। यदि किसी भूमिका को संपादित करने की अनुमति है, तो इसे भी देखने में सक्षम होना चाहिए।

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

newtype Policy = Policy (Role -> Operation) 

इस प्रणाली मैं सार्वजनिक रूप से संपादन योग्य नीति व्यक्त कर सकते हैं के रूप में में:

publicEditable :: Policy 
publicEditable = Policy $ const Edit 

लेकिन प्रकार प्रणाली मुझे इस तरह बेवकूफ नीतियों (कि Edit को Public परमिट लेकिन करने के लिए किसी भी उपयोग से इनकार करते हैं परिभाषित करने से नहीं रोकता Owner):

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

मैं कैसे प्रकार प्रणाली में भूमिका और ऑपरेशन की पदानुक्रमिक प्रकृति व्यक्त कर सकते हैं ?

उत्तर

7

Policy के कन्स्ट्रक्टर तक पहुंच वाले किसी भी व्यक्ति को Policy अलग-अलग ले जाया जा सकता है और संभवतः एक गैरकानूनी फैशन में इसे एक साथ रख सकता है। इस मॉड्यूल के बाहर Policy कन्स्ट्रक्टर का पर्दाफाश न करें। इसके बजाय, उन नीतियों को बनाने के लिए smart constructor प्रदान करें जो अच्छी तरह से गठित होने की गारंटी है और Monoid इंटरफ़ेस का खुलासा किए बिना उन्हें लिखने के लिए खुलासा करें। Policy टाइप अमूर्त को बनाए रखने से यह सुनिश्चित होता है कि इस कोड के अंदर गैरकानूनी नीतियों के परिणामस्वरूप सभी कोड रखा जा सके।

{-# 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) 

नीचे मैं base से Monoid उदाहरणों की एक जोड़ी उधार लेने के लिए GeneralizedNewtypeDeriving उपयोग कर रहा हूँ: the monoid for functions, जो समारोह तीर के माध्यम से बिंदु के लिहाज से एक और monoid लिफ्टों, और the Max newtype, जिसके द्वारा एक Monoid उदाहरण में एक Ord उदाहरण बदल जाता है हमेशा mappend के तर्कों का बड़ा चयन करें।

तो Policy के Monoid उदाहरण स्वचालित रूप से होने वाली नीतियों की रचना Level के आदेश का प्रबंधन करेगा: जब किसी दिए गए भूमिका पर परस्पर विरोधी के स्तर के साथ दो नीतियों रचना हम हमेशा अधिक अनुमोदक एक का चयन करेंगे। यह <>योजक ऑपरेशन बनाता है: आप "डिफ़ॉल्ट" नीति, mempty पर अनुमतियां जोड़कर नीतियों को परिभाषित करते हैं, जो कि किसी को भी अनुमति नहीं देता है।

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

grant एक स्मार्ट निर्माता जो नीतियों Role और Level के आदेश गुण का सम्मान पैदा करता है। ध्यान दें कि मैं भूमिका निभाने की अनुमति देने के लिए >= के साथ भूमिकाओं की तुलना कर रहा हूं ताकि अधिक विशेषाधिकार प्राप्त भूमिकाओं की अनुमति भी मिल सके।

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

can एक अवलोकन जिससे आपको पता चलता एक नीति किसी दिए गए भूमिका के लिए किसी दिए गए पहुँच स्तर अनुदान या नहीं।एक बार मैं >= का उपयोग कर यह सुनिश्चित करने के लिए उपयोग कर रहा हूं कि अधिक अनुमोदित स्तर कम अनुमोदित लोगों को इंगित करें।

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

मुझे आश्चर्य हुआ कि इस मॉड्यूल ने कितना छोटा कोड लिया था! deriving तंत्र पर विशेष रूप से GeneralizedNewtypeDeriving पर झुकाव, प्रकारों को "उबाऊ" कोड के प्रभारी रखने का एक बहुत अच्छा तरीका है ताकि आप महत्वपूर्ण सामग्री पर ध्यान केंद्रित कर सकें।


इन नीतियों का उपयोग इस तरह दिखता है:

module Client where 

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

आप सरल लोगों से बाहर जटिल नीतियों का निर्माण करने के Monoid वर्ग का उपयोग कर सकते हैं।

ownerEdit, contributorView, myPolicy :: Policy 

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

और आप नीतियों का परीक्षण करने के लिए can फ़ंक्शन का उपयोग कर सकते हैं।

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

उदाहरण के लिए:

ghci> canPublicView myPolicy 
False 
+0

I Am क्योंकि 'मैक्स A' एक monoid और सही है कि GHC' Policy' के लिए एक monoid उदाहरण प्राप्त करने में सक्षम है 'एक्स -> monoid y' एक मोनॉयड है। मैं अपना खुद का 'उदाहरण भी प्राप्त कर सकता हूं: '' '(पॉलिसी ए)' मैपेंड '(पॉलिसी बी) = पॉलिसी $ \ r -> अधिकतम (एआर) (बीआर)' ' – homam

+0

हां, हालांकि जीएचसी बिल्कुल वही उत्पन्न करेगा कोड, तो इसे लिखने से परेशान क्यों? –

+0

यह एक बहुत ही सुरुचिपूर्ण समाधान है। धन्यवाद! – homam

3

बेंजामिन हॉजसन के समाधान सरल और अधिक सुंदर है, लेकिन यहाँ एक प्रकार-स्तरीय प्रोग्रामिंग समाधान है, singletons पैकेज की मशीनरी का उपयोग कर।

विचार है कि नीतियों (Role, Operation) tuples, जहां दोनों Role और Operation सूची भर nondecreasing किया जाना चाहिए के प्रकार के स्तर के सूची के रूप में प्रतिनिधित्व कर रहे हैं है। इस तरह, हम एक बेतुका [(Public,Edit),(Owner,View)] अनुमति नहीं दे सकते हैं।

कुछ आवश्यक एक्सटेंशन और आयात:

{-# 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) 

हम डेटाटाइप्स घोषित करने और का उपयोग कर उन्हें singletonize खाका हास्केल:

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]) 

nondecreasing तत्वों के साथ सूची के लिए एक वर्ग:

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

टाइप-स्तरीय सूची के रूप में निर्दिष्ट नीति को देखते हुए, नीति फ़ंक्शन वापस करें:

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 

परीक्षण यह GHCi में:

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

उत्कृष्ट तुलना। स्थिर/गतिशील डिजाइन अंतरिक्ष के साथ अभी भी जॉगलिंग – nicolas

संबंधित मुद्दे