Support Modify
This commit is contained in:
parent
cfaabed84e
commit
51f61cea6c
@ -10,8 +10,8 @@ This library implements (the parts of) [RFC 4511][rfc4511]
|
|||||||
Bind Operation | 4.2 | ✔
|
Bind Operation | 4.2 | ✔
|
||||||
Unbind Operation | 4.3 | ✔
|
Unbind Operation | 4.3 | ✔
|
||||||
Notice of Disconnection | 4.4.1 | ✘
|
Notice of Disconnection | 4.4.1 | ✘
|
||||||
Search Operation | 4.5 | ✔ (partial)
|
Search Operation | 4.5 | ✔†
|
||||||
Modify Operation | 4.6 | ✘
|
Modify Operation | 4.6 | ✔
|
||||||
Add Operation | 4.7 | ✔
|
Add Operation | 4.7 | ✔
|
||||||
Delete Operation | 4.8 | ✔
|
Delete Operation | 4.8 | ✔
|
||||||
Modify DN Operation | 4.9 | ✘
|
Modify DN Operation | 4.9 | ✘
|
||||||
@ -22,6 +22,8 @@ IntermediateResponse Message | 4.13 | ✘
|
|||||||
StartTLS Operation | 4.14 | ✘
|
StartTLS Operation | 4.14 | ✘
|
||||||
LDAP over TLS | - | ✔
|
LDAP over TLS | - | ✔
|
||||||
|
|
||||||
|
†: approximate and extensible matches are untested, so probably do not work
|
||||||
|
|
||||||
```
|
```
|
||||||
% git grep '\bString\b' | wc -l
|
% git grep '\bString\b' | wc -l
|
||||||
2
|
2
|
||||||
|
|||||||
@ -6,13 +6,12 @@ module Ldap.Asn1.FromAsn1
|
|||||||
, next
|
, next
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Applicative (Alternative(..), optional)
|
import Control.Applicative (Alternative(..), liftA2, optional)
|
||||||
import Control.Monad ((>=>), MonadPlus(..))
|
import Control.Monad (MonadPlus(..), (>=>), guard)
|
||||||
import Data.ASN1.Types (ASN1)
|
import Data.ASN1.Types (ASN1)
|
||||||
import qualified Data.ASN1.Types as Asn1
|
import qualified Data.ASN1.Types as Asn1
|
||||||
import Data.Foldable (asum)
|
import Data.Foldable (asum)
|
||||||
import Data.List.NonEmpty (some1)
|
import Data.List.NonEmpty (some1)
|
||||||
import qualified Data.Set as Set
|
|
||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
|
|
||||||
import Ldap.Asn1.Type
|
import Ldap.Asn1.Type
|
||||||
@ -96,7 +95,7 @@ instance FromAsn1 PartialAttribute where
|
|||||||
vs <- many fromAsn1
|
vs <- many fromAsn1
|
||||||
Asn1.End Asn1.Set <- next
|
Asn1.End Asn1.Set <- next
|
||||||
Asn1.End Asn1.Sequence <- next
|
Asn1.End Asn1.Sequence <- next
|
||||||
return (PartialAttribute d (Set.fromList vs))
|
return (PartialAttribute d vs)
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
LDAPResult ::= SEQUENCE {
|
LDAPResult ::= SEQUENCE {
|
||||||
@ -234,6 +233,8 @@ SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
|||||||
|
|
||||||
SearchResultDone ::= [APPLICATION 5] LDAPResult
|
SearchResultDone ::= [APPLICATION 5] LDAPResult
|
||||||
|
|
||||||
|
ModifyResponse ::= [APPLICATION 7] LDAPResult
|
||||||
|
|
||||||
AddResponse ::= [APPLICATION 9] LDAPResult
|
AddResponse ::= [APPLICATION 9] LDAPResult
|
||||||
|
|
||||||
DelResponse ::= [APPLICATION 11] LDAPResult
|
DelResponse ::= [APPLICATION 11] LDAPResult
|
||||||
@ -242,43 +243,22 @@ CompareResponse ::= [APPLICATION 15] LDAPResult
|
|||||||
-}
|
-}
|
||||||
instance FromAsn1 ProtocolServerOp where
|
instance FromAsn1 ProtocolServerOp where
|
||||||
fromAsn1 = asum
|
fromAsn1 = asum
|
||||||
[ do
|
[ fmap (\res -> BindResponse res Nothing) (app 1)
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 1) <- next
|
, fmap (uncurry SearchResultEntry) (app 4)
|
||||||
result <- fromAsn1
|
, fmap SearchResultDone (app 5)
|
||||||
Asn1.End (Asn1.Container Asn1.Application 1) <- next
|
, fmap ModifyResponse (app 7)
|
||||||
return (BindResponse result Nothing)
|
, fmap AddResponse (app 9)
|
||||||
|
, fmap DeleteResponse (app 11)
|
||||||
, do
|
, fmap CompareResponse (app 15)
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 4) <- next
|
|
||||||
ldapDn <- fromAsn1
|
|
||||||
partialAttributeList <- fromAsn1
|
|
||||||
Asn1.End (Asn1.Container Asn1.Application 4) <- next
|
|
||||||
return (SearchResultEntry ldapDn partialAttributeList)
|
|
||||||
|
|
||||||
, do
|
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 5) <- next
|
|
||||||
result <- fromAsn1
|
|
||||||
Asn1.End (Asn1.Container Asn1.Application 5) <- next
|
|
||||||
return (SearchResultDone result)
|
|
||||||
|
|
||||||
, do
|
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 9) <- next
|
|
||||||
result <- fromAsn1
|
|
||||||
Asn1.End (Asn1.Container Asn1.Application 9) <- next
|
|
||||||
return (AddResponse result)
|
|
||||||
|
|
||||||
, do
|
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 11) <- next
|
|
||||||
result <- fromAsn1
|
|
||||||
Asn1.End (Asn1.Container Asn1.Application 11) <- next
|
|
||||||
return (DeleteResponse result)
|
|
||||||
|
|
||||||
, do
|
|
||||||
Asn1.Start (Asn1.Container Asn1.Application 15) <- next
|
|
||||||
result <- fromAsn1
|
|
||||||
Asn1.End (Asn1.Container Asn1.Application 15) <- next
|
|
||||||
return (CompareResponse result)
|
|
||||||
]
|
]
|
||||||
|
where
|
||||||
|
app l = do
|
||||||
|
Asn1.Start (Asn1.Container Asn1.Application x) <- next
|
||||||
|
guard (x == l)
|
||||||
|
res <- fromAsn1
|
||||||
|
Asn1.End (Asn1.Container Asn1.Application y) <- next
|
||||||
|
guard (y == l)
|
||||||
|
return res
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
PartialAttributeList ::= SEQUENCE OF partialAttribute PartialAttribute
|
PartialAttributeList ::= SEQUENCE OF partialAttribute PartialAttribute
|
||||||
@ -290,6 +270,9 @@ instance FromAsn1 PartialAttributeList where
|
|||||||
Asn1.End Asn1.Sequence <- next
|
Asn1.End Asn1.Sequence <- next
|
||||||
return (PartialAttributeList xs)
|
return (PartialAttributeList xs)
|
||||||
|
|
||||||
|
instance (FromAsn1 a, FromAsn1 b) => FromAsn1 (a, b) where
|
||||||
|
fromAsn1 = liftA2 (,) fromAsn1 fromAsn1
|
||||||
|
|
||||||
|
|
||||||
newtype Parser s a = Parser { unParser :: s -> Maybe (s, a) }
|
newtype Parser s a = Parser { unParser :: s -> Maybe (s, a) }
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Data.Foldable (fold, foldMap)
|
|||||||
import Data.Maybe (Maybe, maybe)
|
import Data.Maybe (Maybe, maybe)
|
||||||
import Data.Monoid (Endo(Endo), (<>), mempty)
|
import Data.Monoid (Endo(Endo), (<>), mempty)
|
||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
import Prelude ((.), fromIntegral)
|
import Prelude (Integer, (.), fromIntegral)
|
||||||
|
|
||||||
import Ldap.Asn1.Type
|
import Ldap.Asn1.Type
|
||||||
|
|
||||||
@ -87,11 +87,16 @@ AssertionValue ::= OCTET STRING
|
|||||||
instance ToAsn1 AssertionValue where
|
instance ToAsn1 AssertionValue where
|
||||||
toAsn1 (AssertionValue s) = single (Asn1.OctetString s)
|
toAsn1 (AssertionValue s) = single (Asn1.OctetString s)
|
||||||
|
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
PartialAttribute ::= SEQUENCE {
|
PartialAttribute ::= SEQUENCE {
|
||||||
type AttributeDescription,
|
type AttributeDescription,
|
||||||
vals SET OF value AttributeValue }
|
vals SET OF value AttributeValue }
|
||||||
|
-}
|
||||||
|
instance ToAsn1 PartialAttribute where
|
||||||
|
toAsn1 (PartialAttribute d xs) = sequence (toAsn1 d <> set (foldMap toAsn1 xs))
|
||||||
|
|
||||||
|
{- |
|
||||||
Attribute ::= PartialAttribute(WITH COMPONENTS {
|
Attribute ::= PartialAttribute(WITH COMPONENTS {
|
||||||
...,
|
...,
|
||||||
vals (SIZE(1..MAX))})
|
vals (SIZE(1..MAX))})
|
||||||
@ -151,6 +156,16 @@ SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
|||||||
filter Filter,
|
filter Filter,
|
||||||
attributes AttributeSelection }
|
attributes AttributeSelection }
|
||||||
|
|
||||||
|
ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||||
|
object LDAPDN,
|
||||||
|
changes SEQUENCE OF change SEQUENCE {
|
||||||
|
operation ENUMERATED {
|
||||||
|
add (0),
|
||||||
|
delete (1),
|
||||||
|
replace (2),
|
||||||
|
... },
|
||||||
|
modification PartialAttribute } }
|
||||||
|
|
||||||
AddRequest ::= [APPLICATION 8] SEQUENCE {
|
AddRequest ::= [APPLICATION 8] SEQUENCE {
|
||||||
entry LDAPDN,
|
entry LDAPDN,
|
||||||
attributes AttributeList }
|
attributes AttributeList }
|
||||||
@ -169,8 +184,8 @@ instance ToAsn1 ProtocolClientOp where
|
|||||||
toAsn1 (SearchRequest bo s da sl tl to f a) =
|
toAsn1 (SearchRequest bo s da sl tl to f a) =
|
||||||
application 3 (fold
|
application 3 (fold
|
||||||
[ toAsn1 bo
|
[ toAsn1 bo
|
||||||
, single (Asn1.Enumerated s')
|
, enum s'
|
||||||
, single (Asn1.Enumerated da')
|
, enum da'
|
||||||
, single (Asn1.IntVal (fromIntegral sl))
|
, single (Asn1.IntVal (fromIntegral sl))
|
||||||
, single (Asn1.IntVal (fromIntegral tl))
|
, single (Asn1.IntVal (fromIntegral tl))
|
||||||
, single (Asn1.Boolean to)
|
, single (Asn1.Boolean to)
|
||||||
@ -187,6 +202,14 @@ instance ToAsn1 ProtocolClientOp where
|
|||||||
DerefInSearching -> 1
|
DerefInSearching -> 1
|
||||||
DerefFindingBaseObject -> 2
|
DerefFindingBaseObject -> 2
|
||||||
DerefAlways -> 3
|
DerefAlways -> 3
|
||||||
|
toAsn1 (ModifyRequest dn xs) =
|
||||||
|
application 6 (fold
|
||||||
|
[ toAsn1 dn
|
||||||
|
, sequence (foldMap (\(op, pa) -> sequence (enum (case op of
|
||||||
|
Add -> 0
|
||||||
|
Delete -> 1
|
||||||
|
Replace -> 2) <> toAsn1 pa)) xs)
|
||||||
|
])
|
||||||
toAsn1 (AddRequest dn as) =
|
toAsn1 (AddRequest dn as) =
|
||||||
application 8 (toAsn1 dn <> toAsn1 as)
|
application 8 (toAsn1 dn <> toAsn1 as)
|
||||||
toAsn1 (DeleteRequest (LdapDn (LdapString dn))) =
|
toAsn1 (DeleteRequest (LdapDn (LdapString dn))) =
|
||||||
@ -294,5 +317,8 @@ other c t = single . Asn1.Other c t
|
|||||||
optional :: ToAsn1 a => Maybe a -> Endo [ASN1]
|
optional :: ToAsn1 a => Maybe a -> Endo [ASN1]
|
||||||
optional = maybe mempty toAsn1
|
optional = maybe mempty toAsn1
|
||||||
|
|
||||||
|
enum :: Integer -> Endo [ASN1]
|
||||||
|
enum = single . Asn1.Enumerated
|
||||||
|
|
||||||
single :: a -> Endo [a]
|
single :: a -> Endo [a]
|
||||||
single x = Endo (x :)
|
single x = Endo (x :)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ module Ldap.Asn1.Type where
|
|||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import Data.Int (Int8, Int32)
|
import Data.Int (Int8, Int32)
|
||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.Set (Set)
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +22,7 @@ data ProtocolClientOp =
|
|||||||
| AddRequest LdapDn AttributeList
|
| AddRequest LdapDn AttributeList
|
||||||
| DeleteRequest LdapDn
|
| DeleteRequest LdapDn
|
||||||
| CompareRequest LdapDn AttributeValueAssertion
|
| CompareRequest LdapDn AttributeValueAssertion
|
||||||
|
| ModifyRequest LdapDn [(Operation, PartialAttribute)]
|
||||||
deriving (Show, Eq, Ord)
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
data ProtocolServerOp =
|
data ProtocolServerOp =
|
||||||
@ -30,6 +30,7 @@ data ProtocolServerOp =
|
|||||||
| SearchResultEntry LdapDn PartialAttributeList
|
| SearchResultEntry LdapDn PartialAttributeList
|
||||||
| SearchResultReference (NonEmpty Uri)
|
| SearchResultReference (NonEmpty Uri)
|
||||||
| SearchResultDone (LdapResult)
|
| SearchResultDone (LdapResult)
|
||||||
|
| ModifyResponse LdapResult
|
||||||
| AddResponse LdapResult
|
| AddResponse LdapResult
|
||||||
| DeleteResponse LdapResult
|
| DeleteResponse LdapResult
|
||||||
| CompareResponse LdapResult
|
| CompareResponse LdapResult
|
||||||
@ -154,7 +155,7 @@ newtype AssertionValue = AssertionValue ByteString
|
|||||||
data Attribute = Attribute AttributeDescription (NonEmpty AttributeValue)
|
data Attribute = Attribute AttributeDescription (NonEmpty AttributeValue)
|
||||||
deriving (Show, Eq, Ord)
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
data PartialAttribute = PartialAttribute AttributeDescription (Set AttributeValue)
|
data PartialAttribute = PartialAttribute AttributeDescription [AttributeValue]
|
||||||
deriving (Show, Eq, Ord)
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
newtype LdapDn = LdapDn LdapString
|
newtype LdapDn = LdapDn LdapString
|
||||||
@ -166,6 +167,12 @@ newtype ReferralUris = ReferralUris (NonEmpty Uri)
|
|||||||
newtype Uri = Uri LdapString
|
newtype Uri = Uri LdapString
|
||||||
deriving (Show, Eq, Ord)
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
data Operation =
|
||||||
|
Add
|
||||||
|
| Delete
|
||||||
|
| Replace
|
||||||
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
-- | The LDAPString is a notational convenience to indicate that, although
|
-- | The LDAPString is a notational convenience to indicate that, although
|
||||||
-- strings of LDAPString type encode as ASN.1 OCTET STRING types, the
|
-- strings of LDAPString type encode as ASN.1 OCTET STRING types, the
|
||||||
-- [ISO10646] character set (a superset of [Unicode]) is used, encoded
|
-- [ISO10646] character set (a superset of [Unicode]) is used, encoded
|
||||||
|
|||||||
@ -9,12 +9,12 @@ module Ldap.Client
|
|||||||
, Type.ResultCode(..)
|
, Type.ResultCode(..)
|
||||||
, Async
|
, Async
|
||||||
, with
|
, with
|
||||||
-- * Bind Request
|
-- * Bind Operation
|
||||||
, Dn(..)
|
, Dn(..)
|
||||||
, Password(..)
|
, Password(..)
|
||||||
, BindError(..)
|
, BindError(..)
|
||||||
, bind
|
, bind
|
||||||
-- * Search Request
|
-- * Search Operation
|
||||||
, Attr(..)
|
, Attr(..)
|
||||||
, SearchError(..)
|
, SearchError(..)
|
||||||
, search
|
, search
|
||||||
@ -27,17 +27,21 @@ module Ldap.Client
|
|||||||
, derefAliases
|
, derefAliases
|
||||||
, Filter(..)
|
, Filter(..)
|
||||||
, SearchEntry(..)
|
, SearchEntry(..)
|
||||||
-- * Add Request
|
-- * Modify Operation
|
||||||
|
, ModifyError(..)
|
||||||
|
, Operation(..)
|
||||||
|
, modify
|
||||||
|
-- * Add Operation
|
||||||
, AttrList
|
, AttrList
|
||||||
, AddError(..)
|
, AddError(..)
|
||||||
, add
|
, add
|
||||||
-- * Delete Request
|
-- * Delete Operation
|
||||||
, DeleteError(..)
|
, DeleteError(..)
|
||||||
, delete
|
, delete
|
||||||
-- * Compare Request
|
-- * Compare Operation
|
||||||
, CompareError(..)
|
, CompareError(..)
|
||||||
, compare
|
, compare
|
||||||
-- * Waiting for Request Completion
|
-- * Waiting for Operation Completion
|
||||||
, wait
|
, wait
|
||||||
, waitSTM
|
, waitSTM
|
||||||
) where
|
) where
|
||||||
@ -80,6 +84,7 @@ import Ldap.Client.Search
|
|||||||
, Filter(..)
|
, Filter(..)
|
||||||
, SearchEntry(..)
|
, SearchEntry(..)
|
||||||
)
|
)
|
||||||
|
import Ldap.Client.Modify (ModifyError(..), Operation(..), modify)
|
||||||
import Ldap.Client.Compare (CompareError(..), compare)
|
import Ldap.Client.Compare (CompareError(..), compare)
|
||||||
|
|
||||||
|
|
||||||
@ -92,6 +97,7 @@ data LdapError =
|
|||||||
| ParseError Asn1.ASN1Error
|
| ParseError Asn1.ASN1Error
|
||||||
| BindError BindError
|
| BindError BindError
|
||||||
| SearchError SearchError
|
| SearchError SearchError
|
||||||
|
| ModifyError ModifyError
|
||||||
| AddError AddError
|
| AddError AddError
|
||||||
| DeleteError DeleteError
|
| DeleteError DeleteError
|
||||||
| CompareError CompareError
|
| CompareError CompareError
|
||||||
@ -115,6 +121,7 @@ with host port f = do
|
|||||||
, Handler (return . Left . ParseError)
|
, Handler (return . Left . ParseError)
|
||||||
, Handler (return . Left . BindError)
|
, Handler (return . Left . BindError)
|
||||||
, Handler (return . Left . SearchError)
|
, Handler (return . Left . SearchError)
|
||||||
|
, Handler (return . Left . ModifyError)
|
||||||
, Handler (return . Left . AddError)
|
, Handler (return . Left . AddError)
|
||||||
, Handler (return . Left . DeleteError)
|
, Handler (return . Left . DeleteError)
|
||||||
, Handler (return . Left . CompareError)
|
, Handler (return . Left . CompareError)
|
||||||
@ -194,6 +201,9 @@ dispatch Ldap { client } inq outq =
|
|||||||
let stack = Map.findWithDefault [] mid got
|
let stack = Map.findWithDefault [] mid got
|
||||||
traverse_ (\var -> putTMVar var (op :| stack)) (Map.lookup mid results)
|
traverse_ (\var -> putTMVar var (op :| stack)) (Map.lookup mid results)
|
||||||
return (Map.delete mid got, Map.delete mid results, counter)
|
return (Map.delete mid got, Map.delete mid results, counter)
|
||||||
|
Type.ModifyResponse {} -> do
|
||||||
|
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
|
||||||
|
return (Map.delete mid got, Map.delete mid results, counter)
|
||||||
Type.AddResponse {} -> do
|
Type.AddResponse {} -> do
|
||||||
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
|
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
|
||||||
return (Map.delete mid got, Map.delete mid results, counter)
|
return (Map.delete mid got, Map.delete mid results, counter)
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import Control.Monad.STM (STM, atomically)
|
|||||||
import Data.List.NonEmpty (NonEmpty((:|)))
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
import Data.Typeable (Typeable)
|
import Data.Typeable (Typeable)
|
||||||
|
|
||||||
import Ldap.Client.Internal
|
|
||||||
import qualified Ldap.Asn1.Type as Type
|
import qualified Ldap.Asn1.Type as Type
|
||||||
|
import Ldap.Client.Internal
|
||||||
|
|
||||||
|
|
||||||
data BindError =
|
data BindError =
|
||||||
|
|||||||
66
src/Ldap/Client/Modify.hs
Normal file
66
src/Ldap/Client/Modify.hs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
module Ldap.Client.Modify
|
||||||
|
( ModifyError(..)
|
||||||
|
, Operation(..)
|
||||||
|
, modify
|
||||||
|
, modifyEither
|
||||||
|
, modifyAsync
|
||||||
|
, modifyAsyncSTM
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Exception (Exception)
|
||||||
|
import Control.Monad.STM (STM, atomically)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Typeable (Typeable)
|
||||||
|
|
||||||
|
import qualified Ldap.Asn1.Type as Type
|
||||||
|
import Ldap.Client.Internal
|
||||||
|
|
||||||
|
|
||||||
|
data ModifyError =
|
||||||
|
ModifyInvalidResponse Response
|
||||||
|
| ModifyErrorCode Type.ResultCode Dn Text
|
||||||
|
deriving (Show, Eq, Typeable)
|
||||||
|
|
||||||
|
data Operation =
|
||||||
|
Delete Attr [ByteString]
|
||||||
|
| Add Attr [ByteString]
|
||||||
|
| Replace Attr [ByteString]
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
|
||||||
|
instance Exception ModifyError
|
||||||
|
|
||||||
|
modify :: Ldap -> Dn -> [Operation] -> IO ()
|
||||||
|
modify l dn as =
|
||||||
|
raise =<< modifyEither l dn as
|
||||||
|
|
||||||
|
modifyEither :: Ldap -> Dn -> [Operation] -> IO (Either ModifyError ())
|
||||||
|
modifyEither l dn as =
|
||||||
|
wait =<< modifyAsync l dn as
|
||||||
|
|
||||||
|
modifyAsync :: Ldap -> Dn -> [Operation] -> IO (Async ModifyError ())
|
||||||
|
modifyAsync l dn as =
|
||||||
|
atomically (modifyAsyncSTM l dn as)
|
||||||
|
|
||||||
|
modifyAsyncSTM :: Ldap -> Dn -> [Operation] -> STM (Async ModifyError ())
|
||||||
|
modifyAsyncSTM l (Dn dn) xs =
|
||||||
|
sendRequest l modifyResult
|
||||||
|
(Type.ModifyRequest (Type.LdapDn (Type.LdapString dn)) (map f xs))
|
||||||
|
where
|
||||||
|
f (Delete (Attr k) vs) =
|
||||||
|
(Type.Delete, Type.PartialAttribute (Type.AttributeDescription (Type.LdapString k))
|
||||||
|
(map Type.AttributeValue vs))
|
||||||
|
f (Add (Attr k) vs) =
|
||||||
|
(Type.Add, Type.PartialAttribute (Type.AttributeDescription (Type.LdapString k))
|
||||||
|
(map Type.AttributeValue vs))
|
||||||
|
f (Replace (Attr k) vs) =
|
||||||
|
(Type.Replace, Type.PartialAttribute (Type.AttributeDescription (Type.LdapString k))
|
||||||
|
(map Type.AttributeValue vs))
|
||||||
|
|
||||||
|
modifyResult :: Response -> Either ModifyError ()
|
||||||
|
modifyResult (Type.ModifyResponse (Type.LdapResult code (Type.LdapDn (Type.LdapString dn)) (Type.LdapString msg) _) :| [])
|
||||||
|
| Type.Success <- code = Right ()
|
||||||
|
| otherwise = Left (ModifyErrorCode code (Dn dn) msg)
|
||||||
|
modifyResult res = Left (ModifyInvalidResponse res)
|
||||||
@ -23,8 +23,6 @@ import Data.Int (Int32)
|
|||||||
import Data.List.NonEmpty (NonEmpty((:|)))
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
import qualified Data.List.NonEmpty as NonEmpty
|
import qualified Data.List.NonEmpty as NonEmpty
|
||||||
import Data.Maybe (mapMaybe)
|
import Data.Maybe (mapMaybe)
|
||||||
import Data.Set (Set)
|
|
||||||
import qualified Data.Set as Set
|
|
||||||
import Data.Typeable (Typeable)
|
import Data.Typeable (Typeable)
|
||||||
|
|
||||||
import qualified Ldap.Asn1.Type as Type
|
import qualified Ldap.Asn1.Type as Type
|
||||||
@ -90,7 +88,7 @@ searchResult (Type.SearchResultDone (Type.LdapResult code _ _ _) :| xs)
|
|||||||
Just (SearchEntry (Dn dn) (map h ys))
|
Just (SearchEntry (Dn dn) (map h ys))
|
||||||
g _ = Nothing
|
g _ = Nothing
|
||||||
h (Type.PartialAttribute (Type.AttributeDescription (Type.LdapString x))
|
h (Type.PartialAttribute (Type.AttributeDescription (Type.LdapString x))
|
||||||
y) = (Attr x, Set.map j y)
|
y) = (Attr x, fmap j y)
|
||||||
j (Type.AttributeValue x) = x
|
j (Type.AttributeValue x) = x
|
||||||
searchResult res = Left (SearchInvalidResponse res)
|
searchResult res = Left (SearchInvalidResponse res)
|
||||||
|
|
||||||
@ -193,5 +191,5 @@ data Filter =
|
|||||||
| Attr :=* (Maybe ByteString, [ByteString], Maybe ByteString)
|
| Attr :=* (Maybe ByteString, [ByteString], Maybe ByteString)
|
||||||
| (Maybe Attr, Maybe Attr, Bool) ::= ByteString
|
| (Maybe Attr, Maybe Attr, Bool) ::= ByteString
|
||||||
|
|
||||||
data SearchEntry = SearchEntry Dn (AttrList Set)
|
data SearchEntry = SearchEntry Dn (AttrList [])
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|||||||
@ -9,17 +9,17 @@ import SpecHelper (locally)
|
|||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = do
|
spec = do
|
||||||
it "binds as admin" $ do
|
it "binds as ‘admin’" $ do
|
||||||
res <- locally $ \l -> do
|
res <- locally $ \l -> do
|
||||||
Ldap.bind l (Dn "cn=admin") (Password "secret")
|
Ldap.bind l (Dn "cn=admin") (Password "secret")
|
||||||
res `shouldBe` Right ()
|
res `shouldBe` Right ()
|
||||||
|
|
||||||
it "tries to bind as admin with the wrong password, unsuccessfully" $ do
|
it "tries to bind as ‘admin’ with the wrong password, unsuccessfully" $ do
|
||||||
res <- locally $ \l -> do
|
res <- locally $ \l -> do
|
||||||
Ldap.bind l (Dn "cn=admin") (Password "public")
|
Ldap.bind l (Dn "cn=admin") (Password "public")
|
||||||
res `shouldBe` Left (Ldap.BindError (Ldap.BindErrorCode Ldap.InvalidCredentials))
|
res `shouldBe` Left (Ldap.BindError (Ldap.BindErrorCode Ldap.InvalidCredentials))
|
||||||
|
|
||||||
it "binds as pikachu" $ do
|
it "binds as ‘pikachu’" $ do
|
||||||
res <- locally $ \l -> do
|
res <- locally $ \l -> do
|
||||||
Ldap.bind l (Dn "cn=admin") (Password "secret")
|
Ldap.bind l (Dn "cn=admin") (Password "secret")
|
||||||
Ldap.SearchEntry udn _ : []
|
Ldap.SearchEntry udn _ : []
|
||||||
|
|||||||
62
test/Ldap/Client/ModifySpec.hs
Normal file
62
test/Ldap/Client/ModifySpec.hs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Ldap.Client.ModifySpec (spec) where
|
||||||
|
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Test.Hspec
|
||||||
|
import Ldap.Client as Ldap
|
||||||
|
|
||||||
|
import SpecHelper (locally, charizard, pikachu)
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
let go l f = Ldap.search l (Dn "o=localhost")
|
||||||
|
(Ldap.scope WholeSubtree <> Ldap.typesOnly True)
|
||||||
|
f
|
||||||
|
[]
|
||||||
|
|
||||||
|
context "delete" $ do
|
||||||
|
it "can land ‘charizard’" $ do
|
||||||
|
res <- locally $ \l -> do
|
||||||
|
[x] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") x `shouldBe` Just ["fire", "flying"]
|
||||||
|
|
||||||
|
Ldap.modify l charizard [Attr "type" `Delete` ["flying"]]
|
||||||
|
|
||||||
|
[y] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") y `shouldBe` Just ["fire"]
|
||||||
|
res `shouldBe` Right ()
|
||||||
|
|
||||||
|
it "tries to remove ‘pikachu’'s password, unsuccessfully" $ do
|
||||||
|
res <- locally $ \l -> do
|
||||||
|
Ldap.modify l pikachu [Attr "password" `Delete` []]
|
||||||
|
res `shouldBe` Left
|
||||||
|
(ModifyError (ModifyErrorCode UnwillingToPerform (Dn "o=localhost") "cannot delete password"))
|
||||||
|
|
||||||
|
context "add" $ do
|
||||||
|
it "can feed ‘charizard’" $ do
|
||||||
|
res <- locally $ \l -> do
|
||||||
|
[x] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") x `shouldBe` Just ["fire", "flying"]
|
||||||
|
|
||||||
|
Ldap.modify l charizard [Attr "type" `Add` ["fed"]]
|
||||||
|
|
||||||
|
[y] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") y `shouldBe` Just ["fire", "flying", "fed"]
|
||||||
|
res `shouldBe` Right ()
|
||||||
|
|
||||||
|
context "replace" $ do
|
||||||
|
it "can put ‘charizard’ to sleep" $ do
|
||||||
|
res <- locally $ \l -> do
|
||||||
|
[x] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") x `shouldBe` Just ["fire", "flying"]
|
||||||
|
|
||||||
|
Ldap.modify l charizard [Attr "type" `Replace` ["sleeping"]]
|
||||||
|
|
||||||
|
[y] <- go l (Attr "cn" := "charizard")
|
||||||
|
lookupAttr (Attr "type") y `shouldBe` Just ["sleeping"]
|
||||||
|
res `shouldBe` Right ()
|
||||||
|
|
||||||
|
lookupAttr :: Attr -> SearchEntry -> Maybe [ByteString]
|
||||||
|
lookupAttr a (SearchEntry _ as) = lookup a as
|
||||||
52
test/ldap.js
52
test/ldap.js
@ -47,7 +47,7 @@ var pokemon = [
|
|||||||
attributes: { cn: 'butterfree', evolution: "2", type: ["bug", "flying"], }
|
attributes: { cn: 'butterfree', evolution: "2", type: ["bug", "flying"], }
|
||||||
},
|
},
|
||||||
{ dn: 'cn=pikachu,o=localhost',
|
{ dn: 'cn=pikachu,o=localhost',
|
||||||
attributes: { cn: 'pikachu', evolution: "0", type: ["electric"], password: "i-choose-you" }
|
attributes: { cn: 'pikachu', evolution: "0", type: ["electric"], password: ["i-choose-you"] }
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -133,6 +133,56 @@ server.compare('o=localhost', [], function(req, res, next) {
|
|||||||
return next(new ldapjs.NoSuchObjectError(req.dn.toString()));
|
return next(new ldapjs.NoSuchObjectError(req.dn.toString()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Javascript is helpless
|
||||||
|
Array.prototype.diff = function(arr) {
|
||||||
|
return this.filter(function(idx) { return arr.indexOf(idx) < 0; });
|
||||||
|
};
|
||||||
|
|
||||||
|
server.modify('o=localhost', [], function(req, res, next) {
|
||||||
|
var dn = req.dn.toString();
|
||||||
|
|
||||||
|
for (var i = 0; i < pokemon.length; i++) {
|
||||||
|
if (pokemon[i].dn === dn) {
|
||||||
|
for (var j = 0; j < req.changes.length; j++) {
|
||||||
|
var m = req.changes[j].modification;
|
||||||
|
|
||||||
|
switch (req.changes[j].operation) {
|
||||||
|
case 'add':
|
||||||
|
pokemon[i].attributes[m.type] = pokemon[i].attributes[m.type].concat(m.vals);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
if (m.type === "password") {
|
||||||
|
return next(new ldapjs.UnwillingToPerformError('cannot delete password'));
|
||||||
|
} else {
|
||||||
|
if (m.vals === 0) {
|
||||||
|
delete pokemon[i].attributes[m.type];
|
||||||
|
} else {
|
||||||
|
pokemon[i].attributes[m.type] = pokemon[i].attributes[m.type].diff(m.vals);
|
||||||
|
if (pokemon[i].attributes[m.type].length === 0) {
|
||||||
|
delete pokemon[i].attributes[m.type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'replace':
|
||||||
|
if (m.vals === 0) {
|
||||||
|
delete pokemon[i].attributes[m.type];
|
||||||
|
} else {
|
||||||
|
pokemon[i].attributes[m.type] = m.vals;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
return next(new ldapjs.NoSuchObjectError(dn));
|
||||||
|
});
|
||||||
|
|
||||||
server.listen(port, function() {
|
server.listen(port, function() {
|
||||||
console.log("ldaps://localhost:%d", port);
|
console.log("ldaps://localhost:%d", port);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user