Implement the Abandon operation

This commit is contained in:
Matvey Aksenov 2015-04-23 13:24:00 +00:00
parent fcaf02b044
commit da54207774
7 changed files with 74 additions and 17 deletions

View File

@ -17,7 +17,7 @@ Add Operation | [4.7][4.7] | ✔
Delete Operation | [4.8][4.8] | ✔ Delete Operation | [4.8][4.8] | ✔
Modify DN Operation | [4.9][4.9] | ✔ Modify DN Operation | [4.9][4.9] | ✔
Compare Operation | [4.10][4.10] | ✔ Compare Operation | [4.10][4.10] | ✔
Abandon Operation | [4.11][4.11] | Abandon Operation | [4.11][4.11] |
Extended Operation | [4.12][4.12] | ✔ Extended Operation | [4.12][4.12] | ✔
IntermediateResponse Message | [4.13][4.13] | ✔ IntermediateResponse Message | [4.13][4.13] | ✔
StartTLS Operation | [4.14][4.14] | ✔† StartTLS Operation | [4.14][4.14] | ✔†

View File

@ -34,6 +34,7 @@ library
Ldap.Asn1.ToAsn1 Ldap.Asn1.ToAsn1
Ldap.Asn1.Type Ldap.Asn1.Type
Ldap.Client Ldap.Client
Ldap.Client.Abandon
Ldap.Client.Add Ldap.Client.Add
Ldap.Client.Asn1.ToAsn1 Ldap.Client.Asn1.ToAsn1
Ldap.Client.Bind Ldap.Client.Bind

View File

@ -28,6 +28,7 @@ data ProtocolClientOp =
| DeleteRequest !LdapDn | DeleteRequest !LdapDn
| ModifyDnRequest !LdapDn !RelativeLdapDn !Bool !(Maybe LdapDn) | ModifyDnRequest !LdapDn !RelativeLdapDn !Bool !(Maybe LdapDn)
| CompareRequest !LdapDn !AttributeValueAssertion | CompareRequest !LdapDn !AttributeValueAssertion
| AbandonRequest !Id
| ExtendedRequest !LdapOid !(Maybe ByteString) | ExtendedRequest !LdapOid !(Maybe ByteString)
deriving (Show, Eq) deriving (Show, Eq)

View File

@ -57,12 +57,13 @@ module Ldap.Client
) where ) where
#if __GLASGOW_HASKELL__ < 710 #if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>)) import Control.Applicative ((<$>), (<*>))
#endif #endif
import qualified Control.Concurrent.Async as Async import qualified Control.Concurrent.Async as Async
import Control.Concurrent.STM (atomically, throwSTM) import Control.Concurrent.STM (atomically, throwSTM)
import Control.Concurrent.STM.TMVar (putTMVar) import Control.Concurrent.STM.TMVar (putTMVar)
import Control.Concurrent.STM.TQueue (TQueue, newTQueueIO, writeTQueue, readTQueue) import Control.Concurrent.STM.TQueue (TQueue, newTQueueIO, writeTQueue, readTQueue)
import Control.Concurrent.STM.TVar (newTVarIO)
import Control.Exception (Exception, Handler(..), bracket, throwIO, catch, catches) import Control.Exception (Exception, Handler(..), bracket, throwIO, catch, catches)
import Control.Monad (forever) import Control.Monad (forever)
import qualified Data.ASN1.BinaryEncoding as Asn1 import qualified Data.ASN1.BinaryEncoding as Asn1
@ -118,6 +119,7 @@ import Ldap.Client.Extended (Oid(..), extended)
newLdap :: IO Ldap newLdap :: IO Ldap
newLdap = Ldap newLdap = Ldap
<$> newTQueueIO <$> newTQueueIO
<*> newTVarIO (Type.Id 0)
-- | Various failures that can happen when working with LDAP. -- | Various failures that can happen when working with LDAP.
data LdapError = data LdapError =
@ -214,11 +216,11 @@ dispatch
-> TQueue (Type.LdapMessage Request) -> TQueue (Type.LdapMessage Request)
-> IO a -> IO a
dispatch Ldap { client } inq outq = dispatch Ldap { client } inq outq =
flip fix (Map.empty, 1) $ \loop (!req, !counter) -> flip fix Map.empty $ \loop !req ->
loop =<< atomically (asum loop =<< atomically (asum
[ do New new var <- readTQueue client [ do New mid new var <- readTQueue client
writeTQueue outq (Type.LdapMessage (Type.Id counter) new Nothing) writeTQueue outq (Type.LdapMessage mid new Nothing)
return (Map.insert (Type.Id counter) ([], var) req, counter + 1) return (Map.insert mid ([], var) req)
, do Type.LdapMessage mid op _ , do Type.LdapMessage mid op _
<- readTQueue inq <- readTQueue inq
res <- case op of res <- case op of
@ -233,7 +235,7 @@ dispatch Ldap { client } inq outq =
Type.CompareResponse {} -> done mid op req Type.CompareResponse {} -> done mid op req
Type.ExtendedResponse {} -> probablyDisconnect mid op req Type.ExtendedResponse {} -> probablyDisconnect mid op req
Type.IntermediateResponse {} -> saveUp mid op req Type.IntermediateResponse {} -> saveUp mid op req
return (res, counter) return res
]) ])
where where
saveUp mid op res = saveUp mid op res =

View File

@ -0,0 +1,39 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.11 Abandon> operation.
--
-- This operation comes in two flavours:
--
-- * asynchronous, 'IO' based ('abandonAsync')
--
-- * asynchronous, 'STM' based ('abandonAsyncSTM')
--
-- Of those, the first one ('abandonAsync') is probably the most useful for the typical usecase.
--
-- Synchronous variants are unavailable because the Directory does not
-- respond to @AbandonRequest@s.
module Ldap.Client.Abandon
( abandonAsync
, abandonAsyncSTM
) where
import Control.Monad (void)
import Control.Monad.STM (STM, atomically)
import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal
-- | Perform the Abandon operation asynchronously.
abandonAsync :: Ldap -> Async a -> IO ()
abandonAsync l =
atomically . abandonAsyncSTM l
-- | Perform the Abandon operation asynchronously.
abandonAsyncSTM :: Ldap -> Async a -> STM ()
abandonAsyncSTM l =
void . sendRequest l die . abandonRequest
where
die = error "Ldap.Client.Abandon: do not wait for the response to UnbindRequest"
abandonRequest :: Async a -> Request
abandonRequest (Async i _) =
Type.AbandonRequest i

View File

@ -243,6 +243,10 @@ CompareRequest ::= [APPLICATION 14] SEQUENCE {
ava AttributeValueAssertion } ava AttributeValueAssertion }
@ @
@
AbandonRequest ::= [APPLICATION 16] MessageID
@
@ @
ExtendedRequest ::= [APPLICATION 23] SEQUENCE { ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
requestName [0] LDAPOID, requestName [0] LDAPOID,
@ -296,6 +300,8 @@ instance ToAsn1 ProtocolClientOp where
foldMap (toAsn1 (context <> tag 0)) new) foldMap (toAsn1 (context <> tag 0)) new)
toAsn1 _ (CompareRequest dn av) = toAsn1 _ (CompareRequest dn av) =
sequence (application <> tag 14) (toAsn1 mempty dn <> toAsn1 mempty av) sequence (application <> tag 14) (toAsn1 mempty dn <> toAsn1 mempty av)
toAsn1 _ (AbandonRequest i) =
toAsn1 (application <> tag 16) i
toAsn1 _ (ExtendedRequest oid mv) = toAsn1 _ (ExtendedRequest oid mv) =
sequence (application <> tag 23) sequence (application <> tag 23)
(toAsn1 (context <> tag 0) oid <> (toAsn1 (context <> tag 0) oid <>

View File

@ -6,7 +6,7 @@ module Ldap.Client.Internal
, Ldap(..) , Ldap(..)
, ClientMessage(..) , ClientMessage(..)
, Type.ResultCode(..) , Type.ResultCode(..)
, Async , Async(..)
, AttrList , AttrList
-- * Waiting for Request Completion -- * Waiting for Request Completion
, wait , wait
@ -29,6 +29,7 @@ module Ldap.Client.Internal
import Control.Concurrent.STM (STM, atomically) import Control.Concurrent.STM (STM, atomically)
import Control.Concurrent.STM.TMVar (TMVar, newEmptyTMVar, readTMVar) import Control.Concurrent.STM.TMVar (TMVar, newEmptyTMVar, readTMVar)
import Control.Concurrent.STM.TQueue (TQueue, writeTQueue) import Control.Concurrent.STM.TQueue (TQueue, writeTQueue)
import Control.Concurrent.STM.TVar (TVar, modifyTVar, readTVar)
import Control.Exception (Exception, throwIO) import Control.Exception (Exception, throwIO)
import Control.Monad (void) import Control.Monad (void)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@ -51,18 +52,19 @@ data Host =
-- | A token. All functions that interact with the Directory require one. -- | A token. All functions that interact with the Directory require one.
data Ldap = Ldap data Ldap = Ldap
{ client :: TQueue ClientMessage { client :: TQueue ClientMessage
, counter :: TVar Type.Id
} deriving (Eq) } deriving (Eq)
data ClientMessage = New Request (TMVar (NonEmpty Type.ProtocolServerOp)) data ClientMessage = New Type.Id Request (TMVar (NonEmpty Type.ProtocolServerOp))
type Request = Type.ProtocolClientOp type Request = Type.ProtocolClientOp
type InMessage = Type.ProtocolServerOp type InMessage = Type.ProtocolServerOp
type Response = NonEmpty InMessage type Response = NonEmpty InMessage
-- | Asynchronous LDAP operation. Use 'wait' or 'waitSTM' to wait for its completion. -- | Asynchronous LDAP operation. Use 'wait' or 'waitSTM' to wait for its completion.
data Async a = Async (STM (Either ResponseError a)) data Async a = Async Type.Id (STM (Either ResponseError a))
instance Functor Async where instance Functor Async where
fmap f (Async stm) = Async (fmap (fmap f) stm) fmap f (Async mid stm) = Async mid (fmap (fmap f) stm)
-- | Unique identifier of an LDAP entry. -- | Unique identifier of an LDAP entry.
newtype Dn = Dn Text newtype Dn = Dn Text
@ -103,16 +105,22 @@ wait = atomically . waitSTM
-- should commit. After that, applying 'waitSTM' to the corresponding 'Async' -- should commit. After that, applying 'waitSTM' to the corresponding 'Async'
-- starts to make sense. -- starts to make sense.
waitSTM :: Async a -> STM (Either ResponseError a) waitSTM :: Async a -> STM (Either ResponseError a)
waitSTM (Async stm) = stm waitSTM (Async _ stm) = stm
sendRequest :: Ldap -> (Response -> Either ResponseError a) -> Request -> STM (Async a) sendRequest :: Ldap -> (Response -> Either ResponseError a) -> Request -> STM (Async a)
sendRequest l p msg = sendRequest l p msg =
do var <- newEmptyTMVar do var <- newEmptyTMVar
writeRequest l var msg mid <- newId l
return (Async (fmap p (readTMVar var))) writeRequest l (New mid msg var)
return (Async mid (fmap p (readTMVar var)))
writeRequest :: Ldap -> TMVar Response -> Request -> STM () newId :: Ldap -> STM Type.Id
writeRequest Ldap { client } var msg = writeTQueue client (New msg var) newId Ldap { counter } =
do modifyTVar counter (\(Type.Id mid) -> Type.Id (mid + 1))
readTVar counter
writeRequest :: Ldap -> ClientMessage -> STM ()
writeRequest Ldap { client } = writeTQueue client
raise :: Exception e => Either e a -> IO a raise :: Exception e => Either e a -> IO a
raise = either throwIO return raise = either throwIO return
@ -138,4 +146,4 @@ unbindAsyncSTM :: Ldap -> STM ()
unbindAsyncSTM l = unbindAsyncSTM l =
void (sendRequest l die Type.UnbindRequest) void (sendRequest l die Type.UnbindRequest)
where where
die = error "Ldap.Client: do not wait for the response to UnbindRequest" die = error "Ldap.Client.Internal: do not wait for the response to UnbindRequest"