-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
2,164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
{-# LANGUAGE RecordWildCards #-} | ||
|
||
module HStream.Kafka.Common.Acl where | ||
|
||
import Data.Maybe | ||
import Data.Text (Text) | ||
import qualified Data.Text as T | ||
|
||
import HStream.Kafka.Common.Resource | ||
|
||
-- [0..14] | ||
data AclOperation | ||
= AclOp_UNKNOWN | ||
| AclOp_ANY | ||
| AclOp_ALL | ||
| AclOp_READ | ||
| AclOp_WRITE | ||
| AclOp_CREATE | ||
| AclOp_DELETE | ||
| AclOp_ALTER | ||
| AclOp_DESCRIBE | ||
| AclOp_CLUSTER_ACTION | ||
| AclOp_DESCRIBE_CONFIGS | ||
| AclOp_ALTER_CONFIGS | ||
| AclOp_IDEMPOTENT_WRITE | ||
| AclOp_CREATE_TOKENS | ||
| AclOp_DESCRIBE_TOKENS | ||
deriving (Eq, Enum, Ord) | ||
instance Show AclOperation where | ||
show AclOp_UNKNOWN = "Unknown" | ||
show AclOp_ANY = "Any" | ||
show AclOp_ALL = "All" | ||
show AclOp_READ = "Read" | ||
show AclOp_WRITE = "Write" | ||
show AclOp_CREATE = "Create" | ||
show AclOp_DELETE = "Delete" | ||
show AclOp_ALTER = "Alter" | ||
show AclOp_DESCRIBE = "Describe" | ||
show AclOp_CLUSTER_ACTION = "ClusterAction" | ||
show AclOp_DESCRIBE_CONFIGS = "DescribeConfigs" | ||
show AclOp_ALTER_CONFIGS = "AlterConfigs" | ||
show AclOp_IDEMPOTENT_WRITE = "IdempotentWrite" | ||
show AclOp_CREATE_TOKENS = "CreateTokens" | ||
show AclOp_DESCRIBE_TOKENS = "DescribeTokens" | ||
instance Read AclOperation where | ||
readsPrec _ s = case s of | ||
"Unknown" -> [(AclOp_UNKNOWN, "")] | ||
"Any" -> [(AclOp_ANY, "")] | ||
"All" -> [(AclOp_ALL, "")] | ||
"Read" -> [(AclOp_READ, "")] | ||
"Write" -> [(AclOp_WRITE, "")] | ||
"Create" -> [(AclOp_CREATE, "")] | ||
"Delete" -> [(AclOp_DELETE, "")] | ||
"Alter" -> [(AclOp_ALTER, "")] | ||
"Describe" -> [(AclOp_DESCRIBE, "")] | ||
"ClusterAction" -> [(AclOp_CLUSTER_ACTION, "")] | ||
"DescribeConfigs" -> [(AclOp_DESCRIBE_CONFIGS, "")] | ||
"AlterConfigs" -> [(AclOp_ALTER_CONFIGS, "")] | ||
"IdempotentWrite" -> [(AclOp_IDEMPOTENT_WRITE, "")] | ||
"CreateTokens" -> [(AclOp_CREATE_TOKENS, "")] | ||
"DescribeTokens" -> [(AclOp_DESCRIBE_TOKENS, "")] | ||
_ -> [] | ||
|
||
-- [0..3] | ||
data AclPermissionType | ||
= AclPerm_UNKNOWN | ||
| AclPerm_ANY -- used in filter | ||
| AclPerm_DENY | ||
| AclPerm_ALLOW | ||
deriving (Eq, Enum, Ord) | ||
instance Show AclPermissionType where | ||
show AclPerm_UNKNOWN = "Unknown" | ||
show AclPerm_ANY = "Any" | ||
show AclPerm_DENY = "Deny" | ||
show AclPerm_ALLOW = "Allow" | ||
instance Read AclPermissionType where | ||
readsPrec _ s = case s of | ||
"Unknown" -> [(AclPerm_UNKNOWN, "")] | ||
"Any" -> [(AclPerm_ANY, "")] | ||
"Deny" -> [(AclPerm_DENY, "")] | ||
"Allow" -> [(AclPerm_ALLOW, "")] | ||
_ -> [] | ||
|
||
-- | Data of an access control entry (ACE), which is a 4-tuple of principal, | ||
-- host, operation and permission type. | ||
-- Used in both 'AccessControlEntry' and 'AccessControlEntryFilter', | ||
-- with slightly different field requirements. | ||
data AccessControlEntryData = AccessControlEntryData | ||
{ aceDataPrincipal :: Text | ||
, aceDataHost :: Text | ||
, aceDataOperation :: AclOperation | ||
, aceDataPermissionType :: AclPermissionType | ||
} deriving (Eq, Ord) | ||
instance Show AccessControlEntryData where | ||
show AccessControlEntryData{..} = | ||
"(principal=" <> s_principal <> | ||
", host=" <> s_host <> | ||
", operation=" <> show aceDataOperation <> | ||
", permissionType=" <> show aceDataPermissionType <> ")" | ||
where s_principal = if T.null aceDataPrincipal then "<any>" else T.unpack aceDataPrincipal | ||
s_host = if T.null aceDataHost then "<any>" else T.unpack aceDataHost | ||
|
||
-- | An access control entry (ACE). | ||
-- Requirements: principal and host can not be null. | ||
-- operation can not be 'AclOp_ANY'. | ||
-- permission type can not be 'AclPerm_ANY'. | ||
newtype AccessControlEntry = AccessControlEntry | ||
{ aceData :: AccessControlEntryData | ||
} deriving (Eq, Ord) | ||
instance Show AccessControlEntry where | ||
show AccessControlEntry{..} = show aceData | ||
|
||
-- | A filter which matches access control entry(ACE)s. | ||
-- Requirements: principal and host can both be null. | ||
newtype AccessControlEntryFilter = AccessControlEntryFilter | ||
{ aceFilterData :: AccessControlEntryData | ||
} | ||
instance Show AccessControlEntryFilter where | ||
show AccessControlEntryFilter{..} = show aceFilterData | ||
|
||
instance Matchable AccessControlEntry AccessControlEntryFilter where | ||
-- See org.apache.kafka.common.acl.AccessControlEntryFilter#matches | ||
match AccessControlEntry{..} AccessControlEntryFilter{..} | ||
| not (T.null (aceDataPrincipal aceFilterData)) && | ||
aceDataPrincipal aceFilterData /= aceDataPrincipal aceData = False | ||
| not (T.null (aceDataHost aceFilterData)) && | ||
aceDataHost aceFilterData /= aceDataHost aceData = False | ||
| aceDataOperation aceFilterData /= AclOp_ANY && | ||
aceDataOperation aceFilterData /= aceDataOperation aceData = False | ||
| otherwise = aceDataPermissionType aceFilterData == AclPerm_ANY || | ||
aceDataPermissionType aceFilterData == aceDataPermissionType aceData | ||
matchAtMostOne = isNothing . indefiniteFieldInFilter | ||
indefiniteFieldInFilter AccessControlEntryFilter{ aceFilterData = AccessControlEntryData{..} } | ||
| T.null aceDataPrincipal = Just "Principal is NULL" | ||
| T.null aceDataHost = Just "Host is NULL" | ||
| aceDataOperation == AclOp_ANY = Just "Operation is ANY" | ||
| aceDataOperation == AclOp_UNKNOWN = Just "Operation is UNKNOWN" | ||
| aceDataPermissionType == AclPerm_ANY = Just "Permission type is ANY" | ||
| aceDataPermissionType == AclPerm_UNKNOWN = Just "Permission type is UNKNOWN" | ||
| otherwise = Nothing | ||
|
||
-- | A binding between a resource pattern and an access control entry (ACE). | ||
data AclBinding = AclBinding | ||
{ aclBindingResourcePattern :: ResourcePattern | ||
, aclBindingACE :: AccessControlEntry | ||
} deriving (Eq, Ord) | ||
instance Show AclBinding where | ||
show AclBinding{..} = | ||
"(pattern=" <> show aclBindingResourcePattern <> | ||
", entry=" <> show aclBindingACE <> ")" | ||
|
||
-- | A filter which can match 'AclBinding's. | ||
data AclBindingFilter = AclBindingFilter | ||
{ aclBindingFilterResourcePatternFilter :: ResourcePatternFilter | ||
, aclBindingFilterACEFilter :: AccessControlEntryFilter | ||
} | ||
instance Show AclBindingFilter where | ||
show AclBindingFilter{..} = | ||
"(patternFilter=" <> show aclBindingFilterResourcePatternFilter <> | ||
", entryFilter=" <> show aclBindingFilterACEFilter <> ")" | ||
|
||
instance Matchable AclBinding AclBindingFilter where | ||
-- See org.apache.kafka.common.acl.AclBindingFilter#matches | ||
match AclBinding{..} AclBindingFilter{..} = | ||
match aclBindingResourcePattern aclBindingFilterResourcePatternFilter && | ||
match aclBindingACE aclBindingFilterACEFilter | ||
matchAtMostOne AclBindingFilter{..} = | ||
matchAtMostOne aclBindingFilterResourcePatternFilter && | ||
matchAtMostOne aclBindingFilterACEFilter | ||
indefiniteFieldInFilter AclBindingFilter{..} = | ||
indefiniteFieldInFilter aclBindingFilterResourcePatternFilter <> | ||
indefiniteFieldInFilter aclBindingFilterACEFilter | ||
|
||
-- TODO: validate | ||
-- 1. No UNKNOWN contained | ||
-- 2. resource pattern does not contain '/' | ||
validateAclBinding :: AclBinding -> Either String () | ||
validateAclBinding AclBinding{..} = Right () -- FIXME |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
module HStream.Kafka.Common.AclEntry where | ||
|
||
import Data.Aeson ((.:), (.=)) | ||
import qualified Data.Aeson as Aeson | ||
import qualified Data.Map.Strict as Map | ||
import qualified Data.Set as Set | ||
import Data.Text (Text) | ||
import qualified Data.Text as T | ||
|
||
import HStream.Kafka.Common.Acl | ||
import HStream.Kafka.Common.Resource | ||
import HStream.Kafka.Common.Security | ||
|
||
data AclEntry = AclEntry | ||
{ aclEntryPrincipal :: Principal | ||
, aclEntryHost :: Text | ||
, aclEntryOperation :: AclOperation | ||
, aclEntryPermissionType :: AclPermissionType | ||
} deriving (Eq, Ord) | ||
instance Show AclEntry where | ||
show AclEntry{..} = | ||
s_principal <> | ||
" has " <> show aclEntryPermissionType <> | ||
" permission for operations: " <> show aclEntryOperation <> | ||
" from hosts: " <> s_host | ||
where s_principal = show aclEntryPrincipal | ||
s_host = T.unpack aclEntryHost | ||
instance Aeson.ToJSON AclEntry where | ||
toJSON AclEntry{..} = | ||
Aeson.object [ "host" .= aclEntryHost | ||
, "permissionType" .= show aclEntryPermissionType | ||
, "operation" .= show aclEntryOperation | ||
, "principal" .= show aclEntryPrincipal | ||
] | ||
instance Aeson.FromJSON AclEntry where | ||
parseJSON (Aeson.Object v) = AclEntry | ||
<$> (principalFromText <$> v .: "principal") | ||
<*> v .: "host" | ||
<*> (read <$> v .: "operation") | ||
<*> (read <$> v .: "permissionType") | ||
parseJSON o = fail $ "Invalid AclEntry: " <> show o | ||
|
||
aceToAclEntry :: AccessControlEntry -> AclEntry | ||
aceToAclEntry AccessControlEntry{ aceData = AccessControlEntryData{..} } = | ||
AclEntry{..} | ||
where aclEntryPrincipal = principalFromText aceDataPrincipal | ||
aclEntryHost = aceDataHost | ||
aclEntryOperation = aceDataOperation | ||
aclEntryPermissionType = aceDataPermissionType | ||
|
||
aclEntryToAce :: AclEntry -> AccessControlEntry | ||
aclEntryToAce AclEntry{..} = | ||
AccessControlEntry{ aceData = AccessControlEntryData{..} } | ||
where aceDataPrincipal = T.pack (show aclEntryPrincipal) | ||
aceDataHost = aclEntryHost | ||
aceDataOperation = aclEntryOperation | ||
aceDataPermissionType = aclEntryPermissionType | ||
|
||
type Acls = Set.Set AclEntry | ||
type Version = Int | ||
|
||
defaultVersion :: Version | ||
defaultVersion = 1 | ||
|
||
data AclResourceNode = AclResourceNode | ||
{ aclResNodeVersion :: Version | ||
, aclResNodeAcls :: Acls | ||
} deriving (Show) | ||
instance Aeson.ToJSON AclResourceNode where | ||
toJSON AclResourceNode{..} = | ||
Aeson.object [ "version" .= defaultVersion -- FIXME: version | ||
, "acls" .= aclResNodeAcls | ||
] | ||
instance Aeson.FromJSON AclResourceNode where | ||
parseJSON (Aeson.Object v) = AclResourceNode | ||
<$> v .: "version" | ||
<*> v .: "acls" | ||
parseJSON o = fail $ "Invalid AclResourceNode: " <> show o | ||
|
||
data AclCache = AclCache | ||
{ aclCacheAcls :: Map.Map ResourcePattern Acls | ||
, aclCacheResources :: Map.Map (AccessControlEntry,ResourceType,PatternType) | ||
(Set.Set Text) | ||
} | ||
|
||
------------------------------------ |
Oops, something went wrong.