Add more documentation

Also, add many links to RFC, improve LDAPOID handling, and
other miscellaneous stuff.
This commit is contained in:
Matvey Aksenov 2015-04-08 20:12:39 +00:00
parent aea85536cf
commit e4df6337ab
14 changed files with 354 additions and 135 deletions

View File

@ -7,26 +7,40 @@ ldap-client
This library implements (the parts of) [RFC 4511][rfc4511] This library implements (the parts of) [RFC 4511][rfc4511]
Feature | RFC Section | Support Feature | RFC Section | Support
:--------------------------- |:-----------:|:-----------: :--------------------------- |:---------------:|:-----------:
Bind Operation | 4.2 | ✔ Bind Operation | [4.2][4.2] | ✔
Unbind Operation | 4.3 | ✔ Unbind Operation | [4.3][4.3] | ✔
Unsolicited Notification | 4.4 | ✔ Unsolicited Notification | [4.4][4.4] | ✔
Notice of Disconnection | 4.4.1 | ✔ Notice of Disconnection | [4.4.1][4.4.1] | ✔
Search Operation | 4.5 | ✔\* Search Operation | [4.5][4.5] | ✔\*
Modify Operation | 4.6 | ✔ Modify Operation | [4.6][4.6] | ✔
Add Operation | 4.7 | ✔ Add Operation | [4.7][4.7] | ✔
Delete Operation | 4.8 | ✔ Delete Operation | [4.8][4.8] | ✔
Modify DN Operation | 4.9 | ✔ Modify DN Operation | [4.9][4.9] | ✔
Compare Operation | 4.10 | ✔ Compare Operation | [4.10][4.10] | ✔
Abandon Operation | 4.11 | ✘ Abandon Operation | [4.11][4.11] | ✘
Extended Operation | 4.12 | ✔ Extended Operation | [4.12][4.12] | ✔
IntermediateResponse Message | 4.13 | ✔ IntermediateResponse Message | [4.13][4.13] | ✔
StartTLS Operation | 4.14 | ✔† StartTLS Operation | [4.14][4.14] | ✔†
LDAP over TLS | - | ✔ LDAP over TLS | - | ✔
\* The `:dn` thing is unsupported in Extensible matches \* The `:dn` thing is unsupported in Extensible matches
† Only serves as an example of Extended Operation. It's useless for all practical purposes as it does not actually enable TLS. In other words, use LDAP over TLS instead. † Only serves as an example of Extended Operation. It's useless for all practical purposes as it does not actually enable TLS. In other words, use LDAP over TLS instead.
[rfc4511]: https://tools.ietf.org/html/rfc4511 [rfc4511]: https://tools.ietf.org/html/rfc4511
[LDAP]: https://hackage.haskell.org/package/LDAP [LDAP]: https://hackage.haskell.org/package/LDAP
[4.2]: https://tools.ietf.org/html/rfc4511#section-4.2
[4.3]: https://tools.ietf.org/html/rfc4511#section-4.3
[4.4]: https://tools.ietf.org/html/rfc4511#section-4.4
[4.4.1]: https://tools.ietf.org/html/rfc4511#section-4.4.1
[4.5]: https://tools.ietf.org/html/rfc4511#section-4.5
[4.6]: https://tools.ietf.org/html/rfc4511#section-4.6
[4.7]: https://tools.ietf.org/html/rfc4511#section-4.7
[4.8]: https://tools.ietf.org/html/rfc4511#section-4.8
[4.9]: https://tools.ietf.org/html/rfc4511#section-4.9
[4.10]: https://tools.ietf.org/html/rfc4511#section-4.10
[4.11]: https://tools.ietf.org/html/rfc4511#section-4.11
[4.12]: https://tools.ietf.org/html/rfc4511#section-4.12
[4.13]: https://tools.ietf.org/html/rfc4511#section-4.13
[4.14]: https://tools.ietf.org/html/rfc4511#section-4.14

View File

@ -1,10 +1,8 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
-- | This module contains convertions from ASN.1 to LDAP types.
module Ldap.Asn1.FromAsn1 module Ldap.Asn1.FromAsn1
( FromAsn1(..) ( parseAsn1
, Parser , FromAsn1
, parseAsn1
, parse
, next
) where ) where
#if __GLASGOW_HASKELL__ >= 710 #if __GLASGOW_HASKELL__ >= 710
@ -25,6 +23,13 @@ import Ldap.Asn1.Type
{-# ANN module "HLint: ignore Avoid lambda" #-} {-# ANN module "HLint: ignore Avoid lambda" #-}
-- | Convert a part of ASN.1 stream to a LDAP type returning the remainder of the stream.
parseAsn1 :: FromAsn1 a => [ASN1] -> Maybe ([ASN1], a)
parseAsn1 = parse fromAsn1
-- | ASN.1 stream parsers.
--
-- When it's relevant, instances include the part of RFC describing the encoding.
class FromAsn1 a where class FromAsn1 a where
fromAsn1 :: Parser [ASN1] a fromAsn1 :: Parser [ASN1] a
@ -84,7 +89,9 @@ LDAPOID ::= OCTET STRING -- Constrained to \<numericoid\>
instance FromAsn1 LdapOid where instance FromAsn1 LdapOid where
fromAsn1 = do fromAsn1 = do
Asn1.OctetString s <- next Asn1.OctetString s <- next
return (LdapOid s) case Text.decodeUtf8' s of
Right t -> return (LdapOid t)
Left _ -> empty
{- | {- |
@ @
@ -335,9 +342,12 @@ instance FromAsn1 ProtocolServerOp where
, do , do
Asn1.Start (Asn1.Container Asn1.Application 24) <- next Asn1.Start (Asn1.Container Asn1.Application 24) <- next
res <- fromAsn1 res <- fromAsn1
name <- optional $ do utf8Name <- optional $ do
Asn1.Other Asn1.Context 10 s <- next Asn1.Other Asn1.Context 10 s <- next
return s return s
name <- maybe (return Nothing) (\n -> case Text.decodeUtf8' n of
Left _ -> empty
Right name -> return (Just name)) utf8Name
value <- optional $ do value <- optional $ do
Asn1.Other Asn1.Context 11 s <- next Asn1.Other Asn1.Context 11 s <- next
return s return s
@ -406,9 +416,6 @@ instance MonadPlus (Parser s) where
Parser ma `mplus` Parser mb = Parser ma `mplus` Parser mb =
Parser (\s -> ma s `mplus` mb s) Parser (\s -> ma s `mplus` mb s)
parseAsn1 :: FromAsn1 a => [ASN1] -> Maybe ([ASN1], a)
parseAsn1 = parse fromAsn1
parse :: Parser s a -> s -> Maybe (s, a) parse :: Parser s a -> s -> Maybe (s, a)
parse = unParser parse = unParser

View File

@ -1,3 +1,9 @@
-- | This module contains convertions from LDAP types to ASN.1.
--
-- Various hacks are employed because "asn1-encoding" only encodes to DER, but
-- LDAP demands BER-encoding. So, when a definition looks suspiciously different
-- from the spec in the comment, that's why. I hope all that will be fixed
-- eventually.
module Ldap.Asn1.ToAsn1 module Ldap.Asn1.ToAsn1
( ToAsn1(toAsn1) ( ToAsn1(toAsn1)
) where ) where
@ -15,6 +21,9 @@ import Prelude (Integer, (.), fromIntegral)
import Ldap.Asn1.Type import Ldap.Asn1.Type
-- | Convert a LDAP type to ASN.1.
--
-- When it's relevant, instances include the part of RFC describing the encoding.
class ToAsn1 a where class ToAsn1 a where
toAsn1 :: a -> Endo [ASN1] toAsn1 :: a -> Endo [ASN1]
@ -62,7 +71,7 @@ LDAPOID ::= OCTET STRING -- Constrained to \<numericoid\>
@ @
-} -}
instance ToAsn1 LdapOid where instance ToAsn1 LdapOid where
toAsn1 (LdapOid s) = single (Asn1.OctetString s) toAsn1 (LdapOid s) = single (Asn1.OctetString (Text.encodeUtf8 s))
{- | {- |
@ @
@ -292,7 +301,7 @@ instance ToAsn1 ProtocolClientOp where
application 14 (toAsn1 dn <> sequence (toAsn1 av)) application 14 (toAsn1 dn <> sequence (toAsn1 av))
toAsn1 (ExtendedRequest (LdapOid oid) mv) = toAsn1 (ExtendedRequest (LdapOid oid) mv) =
application 23 (fold application 23 (fold
[ other Asn1.Context 0 oid [ other Asn1.Context 0 (Text.encodeUtf8 oid)
, maybe mempty (other Asn1.Context 1) mv , maybe mempty (other Asn1.Context 1) mv
]) ])

View File

@ -6,56 +6,62 @@ import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text) import Data.Text (Text)
-- | Message envelope. (Section 4.1.1.)
data LdapMessage op = LdapMessage data LdapMessage op = LdapMessage
{ ldapMessageId :: !Id { ldapMessageId :: !Id
, ldapMessageOp :: !op , ldapMessageOp :: !op
, ldapMessageControls :: !(Maybe Controls) , ldapMessageControls :: !(Maybe Controls)
} deriving (Show, Eq, Ord) } deriving (Show, Eq)
-- | Every message being processed has a unique non-zero integer ID. (Section 4.1.1.1.)
newtype Id = Id { unId :: Int32 } newtype Id = Id { unId :: Int32 }
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)
-- | Client requests. The RFC doesn't make a difference between 'ProtocolClientOp'
-- and 'ProtocolServerOp' but it's useful to distinguish between them in Haskell.
data ProtocolClientOp = data ProtocolClientOp =
BindRequest Int8 LdapDn AuthenticationChoice BindRequest !Int8 !LdapDn !AuthenticationChoice
| UnbindRequest | UnbindRequest
| SearchRequest LdapDn Scope DerefAliases Int32 Int32 Bool Filter AttributeSelection | SearchRequest !LdapDn !Scope !DerefAliases !Int32 !Int32 !Bool !Filter !AttributeSelection
| ModifyRequest LdapDn [(Operation, PartialAttribute)] | ModifyRequest !LdapDn ![(Operation, PartialAttribute)]
| AddRequest LdapDn AttributeList | AddRequest !LdapDn !AttributeList
| DeleteRequest LdapDn | DeleteRequest !LdapDn
| ModifyDnRequest LdapDn RelativeLdapDn Bool (Maybe LdapDn) | ModifyDnRequest !LdapDn !RelativeLdapDn !Bool !(Maybe LdapDn)
| CompareRequest LdapDn AttributeValueAssertion | CompareRequest !LdapDn !AttributeValueAssertion
| ExtendedRequest LdapOid (Maybe ByteString) | ExtendedRequest !LdapOid !(Maybe ByteString)
deriving (Show, Eq, Ord) deriving (Show, Eq)
-- | Server responses. The RFC doesn't make a difference between 'ProtocolClientOp'
-- and 'ProtocolServerOp' but it's useful to distinguish between them in Haskell.
data ProtocolServerOp = data ProtocolServerOp =
BindResponse LdapResult (Maybe ByteString) BindResponse !LdapResult !(Maybe ByteString)
| SearchResultEntry LdapDn PartialAttributeList | SearchResultEntry !LdapDn !PartialAttributeList
| SearchResultReference (NonEmpty Uri) | SearchResultReference !(NonEmpty Uri)
| SearchResultDone (LdapResult) | SearchResultDone !(LdapResult)
| ModifyResponse LdapResult | ModifyResponse !LdapResult
| AddResponse LdapResult | AddResponse !LdapResult
| DeleteResponse LdapResult | DeleteResponse !LdapResult
| ModifyDnResponse LdapResult | ModifyDnResponse !LdapResult
| CompareResponse LdapResult | CompareResponse !LdapResult
| ExtendedResponse LdapResult (Maybe LdapOid) (Maybe ByteString) | ExtendedResponse !LdapResult !(Maybe LdapOid) !(Maybe ByteString)
| IntermediateResponse (Maybe LdapOid) (Maybe ByteString) | IntermediateResponse !(Maybe LdapOid) !(Maybe ByteString)
deriving (Show, Eq, Ord) deriving (Show, Eq)
data AuthenticationChoice = Simple ByteString newtype AuthenticationChoice = Simple ByteString
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Scope = data Scope =
BaseObject BaseObject
| SingleLevel | SingleLevel
| WholeSubtree | WholeSubtree
deriving (Show, Eq, Ord) deriving (Show, Eq)
data DerefAliases = data DerefAliases =
NeverDerefAliases NeverDerefAliases
| DerefInSearching | DerefInSearching
| DerefFindingBaseObject | DerefFindingBaseObject
| DerefAlways | DerefAlways
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Filter = data Filter =
And (NonEmpty Filter) And (NonEmpty Filter)
@ -68,40 +74,44 @@ data Filter =
| Present AttributeDescription | Present AttributeDescription
| ApproxMatch AttributeValueAssertion | ApproxMatch AttributeValueAssertion
| ExtensibleMatch MatchingRuleAssertion | ExtensibleMatch MatchingRuleAssertion
deriving (Show, Eq, Ord) deriving (Show, Eq)
data SubstringFilter = SubstringFilter AttributeDescription (NonEmpty Substring) data SubstringFilter = SubstringFilter AttributeDescription (NonEmpty Substring)
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Substring = data Substring =
Initial AssertionValue Initial AssertionValue
| Any AssertionValue | Any AssertionValue
| Final AssertionValue | Final AssertionValue
deriving (Show, Eq, Ord) deriving (Show, Eq)
data MatchingRuleAssertion = MatchingRuleAssertion (Maybe MatchingRuleId) (Maybe AttributeDescription) AssertionValue Bool data MatchingRuleAssertion = MatchingRuleAssertion (Maybe MatchingRuleId) (Maybe AttributeDescription) AssertionValue Bool
deriving (Show, Eq, Ord) deriving (Show, Eq)
-- | Matching rules are defined in Section 4.1.3 of [RFC4512]. A matching
-- rule is identified in the protocol by the printable representation of
-- either its <numericoid> or one of its short name descriptors
-- [RFC4512], e.g., 'caseIgnoreMatch' or '2.5.13.2'. (Section 4.1.8.)
newtype MatchingRuleId = MatchingRuleId LdapString newtype MatchingRuleId = MatchingRuleId LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype AttributeSelection = AttributeSelection [LdapString] newtype AttributeSelection = AttributeSelection [LdapString]
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype AttributeList = AttributeList [Attribute] newtype AttributeList = AttributeList [Attribute]
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype PartialAttributeList = PartialAttributeList [PartialAttribute] newtype PartialAttributeList = PartialAttributeList [PartialAttribute]
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype Controls = Controls [Control] newtype Controls = Controls [Control]
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Control = Control LdapOid Bool (Maybe ByteString) data Control = Control LdapOid Bool (Maybe ByteString)
deriving (Show, Eq, Ord) deriving (Show, Eq)
data LdapResult = LdapResult ResultCode LdapDn LdapString (Maybe ReferralUris) data LdapResult = LdapResult ResultCode LdapDn LdapString (Maybe ReferralUris)
deriving (Show, Eq, Ord) deriving (Show, Eq)
data ResultCode = data ResultCode =
Success Success
@ -143,50 +153,62 @@ data ResultCode =
| ObjectClassModsProhibited | ObjectClassModsProhibited
| AffectsMultipleDSAs | AffectsMultipleDSAs
| Other | Other
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype AttributeDescription = AttributeDescription LdapString newtype AttributeDescription = AttributeDescription LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype AttributeValue = AttributeValue ByteString newtype AttributeValue = AttributeValue ByteString
deriving (Show, Eq, Ord) deriving (Show, Eq)
data AttributeValueAssertion = AttributeValueAssertion AttributeDescription AssertionValue data AttributeValueAssertion = AttributeValueAssertion AttributeDescription AssertionValue
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype AssertionValue = AssertionValue ByteString newtype AssertionValue = AssertionValue ByteString
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Attribute = Attribute AttributeDescription (NonEmpty AttributeValue) data Attribute = Attribute AttributeDescription (NonEmpty AttributeValue)
deriving (Show, Eq, Ord) deriving (Show, Eq)
data PartialAttribute = PartialAttribute AttributeDescription [AttributeValue] data PartialAttribute = PartialAttribute AttributeDescription [AttributeValue]
deriving (Show, Eq, Ord) deriving (Show, Eq)
-- | An LDAPDN is defined to be the representation of a Distinguished Name
-- (DN) after encoding according to the specification in [RFC4514].
newtype LdapDn = LdapDn LdapString newtype LdapDn = LdapDn LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq)
-- | A RelativeLDAPDN is defined to be the representation of a Relative
-- Distinguished Name (RDN) after encoding according to the
-- specification in [RFC4514].
newtype RelativeLdapDn = RelativeLdapDn LdapString newtype RelativeLdapDn = RelativeLdapDn LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype ReferralUris = ReferralUris (NonEmpty Uri) newtype ReferralUris = ReferralUris (NonEmpty Uri)
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype Uri = Uri LdapString newtype Uri = Uri LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq)
data Operation = data Operation =
Add Add
| Delete | Delete
| Replace | Replace
deriving (Show, Eq, Ord) deriving (Show, Eq)
-- | 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
-- following the UTF-8 [RFC3629] algorithm. -- following the UTF-8 [RFC3629] algorithm. (Section 4.1.2.)
newtype LdapString = LdapString Text newtype LdapString = LdapString Text
deriving (Show, Eq, Ord) deriving (Show, Eq)
newtype LdapOid = LdapOid ByteString -- | The LDAPOID is a notational convenience to indicate that the
deriving (Show, Eq, Ord) -- permitted value of this string is a (UTF-8 encoded) dotted-decimal
-- representation of an OBJECT IDENTIFIER. Although an LDAPOID is
-- encoded as an OCTET STRING, values are limited to the definition of
-- \<numericoid\> given in Section 1.4 of [RFC4512].
newtype LdapOid = LdapOid Text
deriving (Show, Eq)

View File

@ -47,6 +47,7 @@ module Ldap.Client
, Password(..) , Password(..)
, AttrList , AttrList
, Attr(..) , Attr(..)
, AttrValue
-- * Re-exports -- * Re-exports
, NonEmpty , NonEmpty
, PortNumber , PortNumber
@ -64,7 +65,6 @@ import Control.Monad (forever)
import qualified Data.ASN1.BinaryEncoding as Asn1 import qualified Data.ASN1.BinaryEncoding as Asn1
import qualified Data.ASN1.Encoding as Asn1 import qualified Data.ASN1.Encoding as Asn1
import qualified Data.ASN1.Error as Asn1 import qualified Data.ASN1.Error as Asn1
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Lazy as ByteString.Lazy import qualified Data.ByteString.Lazy as ByteString.Lazy
import Data.Foldable (asum) import Data.Foldable (asum)
@ -84,7 +84,7 @@ import Ldap.Asn1.ToAsn1 (ToAsn1(toAsn1))
import Ldap.Asn1.FromAsn1 (FromAsn1, parseAsn1) import Ldap.Asn1.FromAsn1 (FromAsn1, parseAsn1)
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
import Ldap.Client.Bind (bind, unbindAsync) import Ldap.Client.Bind (bind)
import Ldap.Client.Search import Ldap.Client.Search
( search ( search
, Search , Search
@ -248,7 +248,7 @@ dispatch Ldap { client } inq outq =
_ -> return req _ -> return req
probablyDisconnect mid op req = done mid op req probablyDisconnect mid op req = done mid op req
noticeOfDisconnection :: ByteString noticeOfDisconnection :: Text
noticeOfDisconnection = fromString "1.3.6.1.4.1.1466.20036" noticeOfDisconnection = fromString "1.3.6.1.4.1.1466.20036"
wrap :: IO a -> IO a wrap :: IO a -> IO a

View File

@ -1,3 +1,16 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.7 Add> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('add')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('addEither')
--
-- * asynchronous, 'IO' based ('addAsync')
--
-- * asynchronous, 'STM' based ('addAsyncSTM')
--
-- Of those, the first one ('add') is probably the most useful for the typical usecase.
module Ldap.Client.Add module Ldap.Client.Add
( add ( add
, addEither , addEither
@ -12,18 +25,27 @@ import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Perform the Add operation synchronously. Raises 'ResponseError' on failures.
add :: Ldap -> Dn -> AttrList NonEmpty -> IO () add :: Ldap -> Dn -> AttrList NonEmpty -> IO ()
add l dn as = add l dn as =
raise =<< addEither l dn as raise =<< addEither l dn as
-- | Perform the Add operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
addEither :: Ldap -> Dn -> AttrList NonEmpty -> IO (Either ResponseError ()) addEither :: Ldap -> Dn -> AttrList NonEmpty -> IO (Either ResponseError ())
addEither l dn as = addEither l dn as =
wait =<< addAsync l dn as wait =<< addAsync l dn as
-- | Perform the Add operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
addAsync :: Ldap -> Dn -> AttrList NonEmpty -> IO (Async ()) addAsync :: Ldap -> Dn -> AttrList NonEmpty -> IO (Async ())
addAsync l dn as = addAsync l dn as =
atomically (addAsyncSTM l dn as) atomically (addAsyncSTM l dn as)
-- | Perform the Add operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
addAsyncSTM :: Ldap -> Dn -> AttrList NonEmpty -> STM (Async ()) addAsyncSTM :: Ldap -> Dn -> AttrList NonEmpty -> STM (Async ())
addAsyncSTM l dn as = addAsyncSTM l dn as =
let req = addRequest dn as in sendRequest l (addResult req) req let req = addRequest dn as in sendRequest l (addResult req) req

View File

@ -1,13 +1,23 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.2 Bind> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('bind')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('bindEither')
--
-- * asynchronous, 'IO' based ('bindAsync')
--
-- * asynchronous, 'STM' based ('bindAsyncSTM')
--
-- Of those, the first one ('bind') is probably the most useful for the typical usecase.
module Ldap.Client.Bind module Ldap.Client.Bind
( bind ( bind
, bindEither , bindEither
, bindAsync , bindAsync
, bindAsyncSTM , bindAsyncSTM
, unbindAsync
, unbindAsyncSTM
) where ) where
import Control.Monad (void)
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
@ -15,18 +25,27 @@ import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Perform the Bind operation synchronously. Raises 'ResponseError' on failures.
bind :: Ldap -> Dn -> Password -> IO () bind :: Ldap -> Dn -> Password -> IO ()
bind l username password = bind l username password =
raise =<< bindEither l username password raise =<< bindEither l username password
-- | Perform the Bind operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
bindEither :: Ldap -> Dn -> Password -> IO (Either ResponseError ()) bindEither :: Ldap -> Dn -> Password -> IO (Either ResponseError ())
bindEither l username password = bindEither l username password =
wait =<< bindAsync l username password wait =<< bindAsync l username password
-- | Perform the Bind operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
bindAsync :: Ldap -> Dn -> Password -> IO (Async ()) bindAsync :: Ldap -> Dn -> Password -> IO (Async ())
bindAsync l username password = bindAsync l username password =
atomically (bindAsyncSTM l username password) atomically (bindAsyncSTM l username password)
-- | Perform the Bind operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
bindAsyncSTM :: Ldap -> Dn -> Password -> STM (Async ()) bindAsyncSTM :: Ldap -> Dn -> Password -> STM (Async ())
bindAsyncSTM l username password = bindAsyncSTM l username password =
let req = bindRequest username password in sendRequest l (bindResult req) req let req = bindRequest username password in sendRequest l (bindResult req) req
@ -45,22 +64,3 @@ bindResult req (Type.BindResponse (Type.LdapResult code (Type.LdapDn (Type.LdapS
| Type.Success <- code = Right () | Type.Success <- code = Right ()
| otherwise = Left (ResponseErrorCode req code (Dn dn) msg) | otherwise = Left (ResponseErrorCode req code (Dn dn) msg)
bindResult req res = Left (ResponseInvalid req res) bindResult req res = Left (ResponseInvalid req res)
-- | Note that 'unbindAsync' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway.
unbindAsync :: Ldap -> IO ()
unbindAsync =
atomically . unbindAsyncSTM
-- | Note that 'unbindAsyncSTM' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway.
unbindAsyncSTM :: Ldap -> STM ()
unbindAsyncSTM l =
void (sendRequest l die Type.UnbindRequest)
where
die = error "Ldap.Client: do not wait for the response to UnbindRequest"

View File

@ -1,3 +1,17 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.10 Compare> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('compare')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('compareEither')
--
-- * asynchronous, 'IO' based ('compareAsync')
--
-- * asynchronous, 'STM' based ('compareAsyncSTM')
--
-- Of those, the first one ('compare') is probably the most useful for the
-- typical usecase.
module Ldap.Client.Compare module Ldap.Client.Compare
( compare ( compare
, compareEither , compareEither
@ -6,7 +20,6 @@ module Ldap.Client.Compare
) where ) where
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
import Prelude hiding (compare) import Prelude hiding (compare)
@ -14,23 +27,32 @@ import Ldap.Client.Internal
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
compare :: Ldap -> Dn -> Attr -> ByteString -> IO Bool -- | Perform the Compare operation synchronously. Raises 'ResponseError' on failures.
compare :: Ldap -> Dn -> Attr -> AttrValue -> IO Bool
compare l dn k v = compare l dn k v =
raise =<< compareEither l dn k v raise =<< compareEither l dn k v
compareEither :: Ldap -> Dn -> Attr -> ByteString -> IO (Either ResponseError Bool) -- | Perform the Compare operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
compareEither :: Ldap -> Dn -> Attr -> AttrValue -> IO (Either ResponseError Bool)
compareEither l dn k v = compareEither l dn k v =
wait =<< compareAsync l dn k v wait =<< compareAsync l dn k v
compareAsync :: Ldap -> Dn -> Attr -> ByteString -> IO (Async Bool) -- | Perform the Compare operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
compareAsync :: Ldap -> Dn -> Attr -> AttrValue -> IO (Async Bool)
compareAsync l dn k v = compareAsync l dn k v =
atomically (compareAsyncSTM l dn k v) atomically (compareAsyncSTM l dn k v)
compareAsyncSTM :: Ldap -> Dn -> Attr -> ByteString -> STM (Async Bool) -- | Perform the Compare operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
compareAsyncSTM :: Ldap -> Dn -> Attr -> AttrValue -> STM (Async Bool)
compareAsyncSTM l dn k v = compareAsyncSTM l dn k v =
let req = compareRequest dn k v in sendRequest l (compareResult req) req let req = compareRequest dn k v in sendRequest l (compareResult req) req
compareRequest :: Dn -> Attr -> ByteString -> Request compareRequest :: Dn -> Attr -> AttrValue -> Request
compareRequest (Dn dn) (Attr k) v = compareRequest (Dn dn) (Attr k) v =
Type.CompareRequest (Type.LdapDn (Type.LdapString dn)) Type.CompareRequest (Type.LdapDn (Type.LdapString dn))
(Type.AttributeValueAssertion (Type.AttributeValueAssertion

View File

@ -1,3 +1,16 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.8 Delete> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('delete')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('deleteEither')
--
-- * asynchronous, 'IO' based ('deleteAsync')
--
-- * asynchronous, 'STM' based ('deleteAsyncSTM')
--
-- Of those, the first one ('delete') is probably the most useful for the typical usecase.
module Ldap.Client.Delete module Ldap.Client.Delete
( delete ( delete
, deleteEither , deleteEither
@ -12,18 +25,27 @@ import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Perform the Delete operation synchronously. Raises 'ResponseError' on failures.
delete :: Ldap -> Dn -> IO () delete :: Ldap -> Dn -> IO ()
delete l dn = delete l dn =
raise =<< deleteEither l dn raise =<< deleteEither l dn
-- | Perform the Delete operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
deleteEither :: Ldap -> Dn -> IO (Either ResponseError ()) deleteEither :: Ldap -> Dn -> IO (Either ResponseError ())
deleteEither l dn = deleteEither l dn =
wait =<< deleteAsync l dn wait =<< deleteAsync l dn
-- | Perform the Delete operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
deleteAsync :: Ldap -> Dn -> IO (Async ()) deleteAsync :: Ldap -> Dn -> IO (Async ())
deleteAsync l dn = deleteAsync l dn =
atomically (deleteAsyncSTM l dn) atomically (deleteAsyncSTM l dn)
-- | Perform the Delete operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
deleteAsyncSTM :: Ldap -> Dn -> STM (Async ()) deleteAsyncSTM :: Ldap -> Dn -> STM (Async ())
deleteAsyncSTM l dn = deleteAsyncSTM l dn =
let req = deleteRequest dn in sendRequest l (deleteResult req) req let req = deleteRequest dn in sendRequest l (deleteResult req) req

View File

@ -1,4 +1,17 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
-- | <https://tools.ietf.org/html/rfc4511#section-4.12 Extended> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('extended')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('extendedEither')
--
-- * asynchronous, 'IO' based ('extendedAsync')
--
-- * asynchronous, 'STM' based ('extendedAsyncSTM')
--
-- Of those, the first one ('extended') is probably the most useful for the typical usecase.
module Ldap.Client.Extended module Ldap.Client.Extended
( extended ( extended
, extendedEither , extendedEither
@ -19,18 +32,27 @@ import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Perform the Extended operation synchronously. Raises 'ResponseError' on failures.
extended :: Ldap -> Oid -> Maybe ByteString -> IO () extended :: Ldap -> Oid -> Maybe ByteString -> IO ()
extended l oid mv = extended l oid mv =
raise =<< extendedEither l oid mv raise =<< extendedEither l oid mv
-- | Perform the Extended operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
extendedEither :: Ldap -> Oid -> Maybe ByteString -> IO (Either ResponseError ()) extendedEither :: Ldap -> Oid -> Maybe ByteString -> IO (Either ResponseError ())
extendedEither l oid mv = extendedEither l oid mv =
wait =<< extendedAsync l oid mv wait =<< extendedAsync l oid mv
-- | Perform the Extended operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
extendedAsync :: Ldap -> Oid -> Maybe ByteString -> IO (Async ()) extendedAsync :: Ldap -> Oid -> Maybe ByteString -> IO (Async ())
extendedAsync l oid mv = extendedAsync l oid mv =
atomically (extendedAsyncSTM l oid mv) atomically (extendedAsyncSTM l oid mv)
-- | Perform the Extended operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
extendedAsyncSTM :: Ldap -> Oid -> Maybe ByteString -> STM (Async ()) extendedAsyncSTM :: Ldap -> Oid -> Maybe ByteString -> STM (Async ())
extendedAsyncSTM l oid mv = extendedAsyncSTM l oid mv =
let req = extendedRequest oid mv in sendRequest l (extendedResult req) req let req = extendedRequest oid mv in sendRequest l (extendedResult req) req

View File

@ -12,6 +12,8 @@ module Ldap.Client.Internal
-- * Waiting for Request Completion -- * Waiting for Request Completion
, wait , wait
, waitSTM , waitSTM
, unbindAsync
, unbindAsyncSTM
-- * Misc -- * Misc
, Response , Response
, ResponseError(..) , ResponseError(..)
@ -22,6 +24,7 @@ module Ldap.Client.Internal
, RelativeDn(..) , RelativeDn(..)
, Password(..) , Password(..)
, Attr(..) , Attr(..)
, AttrValue
, unAttr , unAttr
) where ) where
@ -29,6 +32,7 @@ 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.Exception (Exception, throwIO) import Control.Exception (Exception, throwIO)
import Control.Monad (void)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text) import Data.Text (Text)
@ -58,20 +62,18 @@ data Async a = Async (STM (Either ResponseError a))
instance Functor Async where instance Functor Async where
fmap f (Async stm) = Async (fmap (fmap f) stm) fmap f (Async stm) = Async (fmap (fmap f) stm)
newtype Dn = Dn Text newtype Dn = Dn Text
deriving (Show, Eq) deriving (Show, Eq)
newtype RelativeDn = RelativeDn Text newtype RelativeDn = RelativeDn Text
deriving (Show, Eq) deriving (Show, Eq)
newtype Oid = Oid ByteString newtype Oid = Oid Text
deriving (Show, Eq) deriving (Show, Eq)
newtype Password = Password ByteString newtype Password = Password ByteString
deriving (Show, Eq) deriving (Show, Eq)
data ResponseError = data ResponseError =
ResponseInvalid Request Response ResponseInvalid Request Response
| ResponseErrorCode Request Type.ResultCode Dn Text | ResponseErrorCode Request Type.ResultCode Dn Text
@ -79,26 +81,24 @@ data ResponseError =
instance Exception ResponseError instance Exception ResponseError
newtype Attr = Attr Text newtype Attr = Attr Text
deriving (Show, Eq) deriving (Show, Eq)
type AttrList f = [(Attr, f ByteString)] type AttrValue = ByteString
type AttrList f = [(Attr, f AttrValue)]
-- 'Attr' unwrapper. This is a separate function not to turn 'Attr''s -- 'Attr' unwrapper. This is a separate function not to turn 'Attr''s
-- 'Show' instance into complete and utter shit. -- 'Show' instance into complete and utter shit.
unAttr :: Attr -> Text unAttr :: Attr -> Text
unAttr (Attr a) = a unAttr (Attr a) = a
wait :: Async a -> IO (Either ResponseError a) wait :: Async a -> IO (Either ResponseError a)
wait = atomically . waitSTM wait = atomically . waitSTM
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
@ -110,3 +110,22 @@ writeRequest Ldap { client } var msg = writeTQueue client (New msg var)
raise :: Exception e => Either e a -> IO a raise :: Exception e => Either e a -> IO a
raise = either throwIO return raise = either throwIO return
-- | Note that 'unbindAsync' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway.
unbindAsync :: Ldap -> IO ()
unbindAsync =
atomically . unbindAsyncSTM
-- | Note that 'unbindAsyncSTM' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway.
unbindAsyncSTM :: Ldap -> STM ()
unbindAsyncSTM l =
void (sendRequest l die Type.UnbindRequest)
where
die = error "Ldap.Client: do not wait for the response to UnbindRequest"

View File

@ -1,3 +1,19 @@
-- | <https://tools.ietf.org/html/rfc4511#section-4.6 Modify> and
-- <https://tools.ietf.org/html/rfc4511#section-4.9 Modify DN> operations.
--
-- These operations come in four flavours:
--
-- * synchronous, exception throwing ('modify' / 'modifyDn')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@
-- ('modifyEither' / 'modifyDnEither')
--
-- * asynchronous, 'IO' based ('modifyAsync' / 'modifyDnAsync')
--
-- * asynchronous, 'STM' based ('modifyAsyncSTM' / 'modifyDnAsyncSTM')
--
-- Of those, the first one ('modify' / 'modifyDn') is probably the most
-- useful for the typical usecase.
module Ldap.Client.Modify module Ldap.Client.Modify
( Operation(..) ( Operation(..)
, modify , modify
@ -11,31 +27,40 @@ module Ldap.Client.Modify
) where ) where
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Type of modification being performed.
data Operation = data Operation =
Delete Attr [ByteString] Delete Attr [AttrValue] -- ^ Delete values from the attribute. Deletes the attribute if the list is empty or all current values are listed.
| Add Attr [ByteString] | Add Attr [AttrValue] -- ^ Add values to the attribute, creating it if necessary.
| Replace Attr [ByteString] | Replace Attr [AttrValue] -- ^ Replace all existing values of the attribute with the new list. Deletes the attribute if the list is empty.
deriving (Show, Eq) deriving (Show, Eq)
-- | Perform the Modify operation synchronously. Raises 'ResponseError' on failures.
modify :: Ldap -> Dn -> [Operation] -> IO () modify :: Ldap -> Dn -> [Operation] -> IO ()
modify l dn as = modify l dn as =
raise =<< modifyEither l dn as raise =<< modifyEither l dn as
-- | Perform the Modify operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
modifyEither :: Ldap -> Dn -> [Operation] -> IO (Either ResponseError ()) modifyEither :: Ldap -> Dn -> [Operation] -> IO (Either ResponseError ())
modifyEither l dn as = modifyEither l dn as =
wait =<< modifyAsync l dn as wait =<< modifyAsync l dn as
-- | Perform the Modify operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
modifyAsync :: Ldap -> Dn -> [Operation] -> IO (Async ()) modifyAsync :: Ldap -> Dn -> [Operation] -> IO (Async ())
modifyAsync l dn as = modifyAsync l dn as =
atomically (modifyAsyncSTM l dn as) atomically (modifyAsyncSTM l dn as)
-- | Perform the Modify operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
modifyAsyncSTM :: Ldap -> Dn -> [Operation] -> STM (Async ()) modifyAsyncSTM :: Ldap -> Dn -> [Operation] -> STM (Async ())
modifyAsyncSTM l dn xs = modifyAsyncSTM l dn xs =
let req = modifyRequest dn xs in sendRequest l (modifyResult req) req let req = modifyRequest dn xs in sendRequest l (modifyResult req) req
@ -61,18 +86,27 @@ modifyResult req (Type.ModifyResponse (Type.LdapResult code (Type.LdapDn (Type.L
modifyResult req res = Left (ResponseInvalid req res) modifyResult req res = Left (ResponseInvalid req res)
-- | Perform the Modify DN operation synchronously. Raises 'ResponseError' on failures.
modifyDn :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO () modifyDn :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO ()
modifyDn l dn rdn del new = modifyDn l dn rdn del new =
raise =<< modifyDnEither l dn rdn del new raise =<< modifyDnEither l dn rdn del new
-- | Perform the Modify DN operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
modifyDnEither :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Either ResponseError ()) modifyDnEither :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Either ResponseError ())
modifyDnEither l dn rdn del new = modifyDnEither l dn rdn del new =
wait =<< modifyDnAsync l dn rdn del new wait =<< modifyDnAsync l dn rdn del new
-- | Perform the Modify DN operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
modifyDnAsync :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Async ()) modifyDnAsync :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Async ())
modifyDnAsync l dn rdn del new = modifyDnAsync l dn rdn del new =
atomically (modifyDnAsyncSTM l dn rdn del new) atomically (modifyDnAsyncSTM l dn rdn del new)
-- | Perform the Modify DN operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
modifyDnAsyncSTM :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> STM (Async ()) modifyDnAsyncSTM :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> STM (Async ())
modifyDnAsyncSTM l dn rdn del new = modifyDnAsyncSTM l dn rdn del new =
let req = modifyDnRequest dn rdn del new in sendRequest l (modifyDnResult req) req let req = modifyDnRequest dn rdn del new in sendRequest l (modifyDnResult req) req

View File

@ -1,5 +1,18 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
-- | <https://tools.ietf.org/html/rfc4511#section-4.5 Search> operation.
--
-- This operation comes in four flavours:
--
-- * synchronous, exception throwing ('search')
--
-- * synchronous, returning 'Either' 'ResponseError' @()@ ('searchEither')
--
-- * asynchronous, 'IO' based ('searchAsync')
--
-- * asynchronous, 'STM' based ('searchAsyncSTM')
--
-- Of those, the first one ('search') is probably the most useful for the typical usecase.
module Ldap.Client.Search module Ldap.Client.Search
( search ( search
, searchEither , searchEither
@ -18,7 +31,6 @@ module Ldap.Client.Search
) where ) where
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.ByteString (ByteString)
import Data.Int (Int32) 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
@ -33,10 +45,13 @@ import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Perform the Search operation synchronously. Raises 'ResponseError' on failures.
search :: Ldap -> Dn -> Mod Search -> Filter -> [Attr] -> IO [SearchEntry] search :: Ldap -> Dn -> Mod Search -> Filter -> [Attr] -> IO [SearchEntry]
search l base opts flt attributes = search l base opts flt attributes =
raise =<< searchEither l base opts flt attributes raise =<< searchEither l base opts flt attributes
-- | Perform the Search operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
searchEither searchEither
:: Ldap :: Ldap
-> Dn -> Dn
@ -47,11 +62,23 @@ searchEither
searchEither l base opts flt attributes = searchEither l base opts flt attributes =
wait =<< searchAsync l base opts flt attributes wait =<< searchAsync l base opts flt attributes
-- | Perform the Search operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
searchAsync :: Ldap -> Dn -> Mod Search -> Filter -> [Attr] -> IO (Async [SearchEntry]) searchAsync :: Ldap -> Dn -> Mod Search -> Filter -> [Attr] -> IO (Async [SearchEntry])
searchAsync l base opts flt attributes = searchAsync l base opts flt attributes =
atomically (searchAsyncSTM l base opts flt attributes) atomically (searchAsyncSTM l base opts flt attributes)
searchAsyncSTM :: Ldap -> Dn -> Mod Search -> Filter -> [Attr] -> STM (Async [SearchEntry]) -- | Perform the Search operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
searchAsyncSTM
:: Ldap
-> Dn
-> Mod Search
-> Filter
-> [Attr]
-> STM (Async [SearchEntry])
searchAsyncSTM l base opts flt attributes = searchAsyncSTM l base opts flt attributes =
let req = searchRequest base opts flt attributes in sendRequest l (searchResult req) req let req = searchRequest base opts flt attributes in sendRequest l (searchResult req) req
@ -167,12 +194,12 @@ data Filter =
| And (NonEmpty Filter) | And (NonEmpty Filter)
| Or (NonEmpty Filter) | Or (NonEmpty Filter)
| Present Attr | Present Attr
| Attr := ByteString | Attr := AttrValue
| Attr :>= ByteString | Attr :>= AttrValue
| Attr :<= ByteString | Attr :<= AttrValue
| Attr :~= ByteString | Attr :~= AttrValue
| Attr :=* (Maybe ByteString, [ByteString], Maybe ByteString) | Attr :=* (Maybe AttrValue, [AttrValue], Maybe AttrValue)
| (Maybe Attr, Maybe Attr, Bool) ::= ByteString | (Maybe Attr, Maybe Attr, Bool) ::= AttrValue
data SearchEntry = SearchEntry Dn (AttrList []) data SearchEntry = SearchEntry Dn (AttrList [])
deriving (Show, Eq) deriving (Show, Eq)

View File

@ -1,7 +1,6 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Ldap.Client.ModifySpec (spec) where module Ldap.Client.ModifySpec (spec) where
import Data.ByteString (ByteString)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Test.Hspec import Test.Hspec
import qualified Ldap.Asn1.Type as Ldap.Type import qualified Ldap.Asn1.Type as Ldap.Type
@ -87,5 +86,5 @@ spec = do
] ]
res `shouldBe` Right () res `shouldBe` Right ()
lookupAttr :: Attr -> SearchEntry -> Maybe [ByteString] lookupAttr :: Attr -> SearchEntry -> Maybe [AttrValue]
lookupAttr a (SearchEntry _ as) = lookup a as lookupAttr a (SearchEntry _ as) = lookup a as