Add even more documentation

This commit is contained in:
Matvey Aksenov 2015-04-11 16:15:46 +00:00
parent e4df6337ab
commit 79283cd3df
13 changed files with 166 additions and 113 deletions

View File

@ -60,7 +60,7 @@ login conf =
fix $ \loop -> do fix $ \loop -> do
uid <- prompt "Username: " uid <- prompt "Username: "
us <- Ldap.search l (base conf) us <- Ldap.search l (base conf)
(scope WholeSubtree <> typesOnly True) (typesOnly True)
(And [ Attr "objectClass" := "Person" (And [ Attr "objectClass" := "Person"
, Attr "uid" := Text.encodeUtf8 uid , Attr "uid" := Text.encodeUtf8 uid
]) ])

View File

@ -47,45 +47,51 @@ data ProtocolServerOp =
| IntermediateResponse !(Maybe LdapOid) !(Maybe ByteString) | IntermediateResponse !(Maybe LdapOid) !(Maybe ByteString)
deriving (Show, Eq) deriving (Show, Eq)
-- | Not really a choice until SASL is supported.
newtype AuthenticationChoice = Simple ByteString newtype AuthenticationChoice = Simple ByteString
deriving (Show, Eq) deriving (Show, Eq)
-- | Scope of the search to be performed.
data Scope = data Scope =
BaseObject BaseObject -- ^ Constrained to the entry named by baseObject.
| SingleLevel | SingleLevel -- ^ Constrained to the immediate subordinates of the entry named by baseObject.
| WholeSubtree | WholeSubtree -- ^ Constrained to the entry named by baseObject and to all its subordinates.
deriving (Show, Eq) deriving (Show, Eq)
-- | An indicator as to whether or not alias entries (as defined in
-- [RFC4512]) are to be dereferenced during stages of the Search
-- operation.
data DerefAliases = data DerefAliases =
NeverDerefAliases NeverDerefAliases -- ^ Do not dereference aliases in searching or in locating the base object of the Search.
| DerefInSearching | DerefInSearching -- ^ While searching subordinates of the base object, dereference any alias within the search scope.
| DerefFindingBaseObject | DerefFindingBaseObject -- ^ Dereference aliases in locating the base object of the Search.
| DerefAlways | DerefAlways -- ^ Dereference aliases both in searching and in locating the base object of the Search.
deriving (Show, Eq) deriving (Show, Eq)
-- | Conditions that must be fulfilled in order for the Search to match a given entry.
data Filter = data Filter =
And (NonEmpty Filter) And !(NonEmpty Filter) -- ^ All filters evaluate to @TRUE@
| Or (NonEmpty Filter) | Or !(NonEmpty Filter) -- ^ Any filter evaluates to @TRUE@
| Not Filter | Not Filter -- ^ Filter evaluates to @FALSE@
| EqualityMatch AttributeValueAssertion | EqualityMatch AttributeValueAssertion -- ^ @EQUALITY@ rule returns @TRUE@
| Substrings SubstringFilter | Substrings SubstringFilter -- ^ @SUBSTR@ rule returns @TRUE@
| GreaterOrEqual AttributeValueAssertion | GreaterOrEqual AttributeValueAssertion -- ^ @ORDERING@ rule returns @FALSE@
| LessOrEqual AttributeValueAssertion | LessOrEqual AttributeValueAssertion -- ^ @ORDERING@ or @EQUALITY@ rule returns @TRUE@
| Present AttributeDescription | Present AttributeDescription -- ^ Attribute is present in the entry
| ApproxMatch AttributeValueAssertion | ApproxMatch AttributeValueAssertion -- ^ Same as 'EqualityMatch' for most servers
| ExtensibleMatch MatchingRuleAssertion | ExtensibleMatch MatchingRuleAssertion
deriving (Show, Eq) deriving (Show, Eq)
data SubstringFilter = SubstringFilter AttributeDescription (NonEmpty Substring) data SubstringFilter = SubstringFilter !AttributeDescription !(NonEmpty Substring)
deriving (Show, Eq) deriving (Show, Eq)
data Substring = data Substring =
Initial AssertionValue Initial !AssertionValue
| Any AssertionValue | Any !AssertionValue
| Final AssertionValue | Final !AssertionValue
deriving (Show, Eq) deriving (Show, Eq)
data MatchingRuleAssertion = MatchingRuleAssertion (Maybe MatchingRuleId) (Maybe AttributeDescription) AssertionValue Bool data MatchingRuleAssertion = MatchingRuleAssertion !(Maybe MatchingRuleId) !(Maybe AttributeDescription) !AssertionValue !Bool
deriving (Show, Eq) deriving (Show, Eq)
-- | Matching rules are defined in Section 4.1.3 of [RFC4512]. A matching -- | Matching rules are defined in Section 4.1.3 of [RFC4512]. A matching
@ -107,12 +113,13 @@ newtype PartialAttributeList = PartialAttributeList [PartialAttribute]
newtype Controls = Controls [Control] newtype Controls = Controls [Control]
deriving (Show, Eq) deriving (Show, Eq)
data Control = Control LdapOid Bool (Maybe ByteString) data Control = Control !LdapOid !Bool !(Maybe ByteString)
deriving (Show, Eq) deriving (Show, Eq)
data LdapResult = LdapResult ResultCode LdapDn LdapString (Maybe ReferralUris) data LdapResult = LdapResult !ResultCode !LdapDn !LdapString !(Maybe ReferralUris)
deriving (Show, Eq) deriving (Show, Eq)
-- | LDAP operation's result.
data ResultCode = data ResultCode =
Success Success
| OperationError | OperationError
@ -161,16 +168,16 @@ newtype AttributeDescription = AttributeDescription LdapString
newtype AttributeValue = AttributeValue ByteString newtype AttributeValue = AttributeValue ByteString
deriving (Show, Eq) deriving (Show, Eq)
data AttributeValueAssertion = AttributeValueAssertion AttributeDescription AssertionValue data AttributeValueAssertion = AttributeValueAssertion !AttributeDescription !AssertionValue
deriving (Show, Eq) deriving (Show, Eq)
newtype AssertionValue = AssertionValue ByteString newtype AssertionValue = AssertionValue ByteString
deriving (Show, Eq) deriving (Show, Eq)
data Attribute = Attribute AttributeDescription (NonEmpty AttributeValue) data Attribute = Attribute !AttributeDescription !(NonEmpty AttributeValue)
deriving (Show, Eq) deriving (Show, Eq)
data PartialAttribute = PartialAttribute AttributeDescription [AttributeValue] data PartialAttribute = PartialAttribute !AttributeDescription ![AttributeValue]
deriving (Show, Eq) deriving (Show, Eq)

View File

@ -2,6 +2,7 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
-- | Pure Haskell LDAP client library.
module Ldap.Client module Ldap.Client
( Host(..) ( Host(..)
, Ldap , Ldap
@ -11,6 +12,7 @@ module Ldap.Client
, Async , Async
, with , with
-- * Bind -- * Bind
, Password(..)
, bind , bind
-- * Search -- * Search
, search , search
@ -18,11 +20,12 @@ module Ldap.Client
-- ** Search modifiers -- ** Search modifiers
, Search , Search
, Mod , Mod
, scope
, Type.Scope(..) , Type.Scope(..)
, scope
, size , size
, time , time
, typesOnly , typesOnly
, Type.DerefAliases(..)
, derefAliases , derefAliases
, Filter(..) , Filter(..)
-- * Modify -- * Modify
@ -33,21 +36,20 @@ module Ldap.Client
-- * Delete -- * Delete
, delete , delete
-- * ModifyDn -- * ModifyDn
, RelativeDn(..)
, modifyDn , modifyDn
-- * Compare -- * Compare
, compare , compare
-- * Extended -- * Extended
, Oid(..)
, extended , extended
-- * Waiting for completion -- * Waiting for completion
, wait , wait
-- * Miscellanous -- * Miscellanous
, Dn(..) , Dn(..)
, RelativeDn(..)
, Oid(..)
, Password(..)
, AttrList
, Attr(..) , Attr(..)
, AttrValue , AttrValue
, AttrList
-- * Re-exports -- * Re-exports
, NonEmpty , NonEmpty
, PortNumber , PortNumber
@ -74,6 +76,9 @@ import qualified Data.Map.Strict as Map
import Data.Monoid (Endo(appEndo)) import Data.Monoid (Endo(appEndo))
import Data.String (fromString) import Data.String (fromString)
import Data.Text (Text) import Data.Text (Text)
#if __GLASGOW_HASKELL__ < 710
import Data.Traversable (traverse)
#endif
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Network.Connection (Connection) import Network.Connection (Connection)
import qualified Network.Connection as Conn import qualified Network.Connection as Conn
@ -84,7 +89,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) import Ldap.Client.Bind (Password(..), bind)
import Ldap.Client.Search import Ldap.Client.Search
( search ( search
, Search , Search
@ -97,11 +102,11 @@ import Ldap.Client.Search
, Filter(..) , Filter(..)
, SearchEntry(..) , SearchEntry(..)
) )
import Ldap.Client.Modify (Operation(..), modify, modifyDn) import Ldap.Client.Modify (Operation(..), modify, RelativeDn(..), modifyDn)
import Ldap.Client.Add (add) import Ldap.Client.Add (add)
import Ldap.Client.Delete (delete) import Ldap.Client.Delete (delete)
import Ldap.Client.Compare (compare) import Ldap.Client.Compare (compare)
import Ldap.Client.Extended (extended) import Ldap.Client.Extended (Oid(..), extended)
{-# ANN module "HLint: ignore Use first" #-} {-# ANN module "HLint: ignore Use first" #-}
@ -110,11 +115,12 @@ newLdap :: IO Ldap
newLdap = Ldap newLdap = Ldap
<$> newTQueueIO <$> newTQueueIO
-- | Various failures that can happen when working with LDAP.
data LdapError = data LdapError =
IOError IOError IOError IOError -- ^ Network failure.
| ParseError Asn1.ASN1Error | ParseError Asn1.ASN1Error -- ^ Invalid ASN.1 data received from the server.
| ResponseError ResponseError | ResponseError ResponseError -- ^ An LDAP operation failed.
| DisconnectError Disconnect | DisconnectError Disconnect -- ^ Notice of Disconnection has been received.
deriving (Show, Eq) deriving (Show, Eq)
newtype WrappedIOError = WrappedIOError IOError newtype WrappedIOError = WrappedIOError IOError
@ -128,6 +134,8 @@ data Disconnect = Disconnect Type.ResultCode Dn Text
instance Exception Disconnect instance Exception Disconnect
-- | The entrypoint into LDAP. -- | The entrypoint into LDAP.
--
-- It catches all LDAP-related exceptions.
with :: Host -> PortNumber -> (Ldap -> IO a) -> IO (Either LdapError a) with :: Host -> PortNumber -> (Ldap -> IO a) -> IO (Either LdapError a)
with host port f = do with host port f = do
context <- Conn.initConnectionContext context <- Conn.initConnectionContext
@ -135,11 +143,13 @@ with host port f = do
bracket newLdap unbindAsync (\l -> do bracket newLdap unbindAsync (\l -> do
inq <- newTQueueIO inq <- newTQueueIO
outq <- newTQueueIO outq <- newTQueueIO
Async.withAsync (input inq conn) $ \i -> as <- traverse Async.async
Async.withAsync (output outq conn) $ \o -> [ input inq conn
Async.withAsync (dispatch l inq outq) $ \d -> , output outq conn
Async.withAsync (f l) $ \u -> , dispatch l inq outq
fmap (Right . snd) (Async.waitAnyCancel [i, o, d, u]))) , f l
]
fmap (Right . snd) (Async.waitAnyCancel as)))
`catches` `catches`
[ Handler (\(WrappedIOError e) -> return (Left (IOError e))) [ Handler (\(WrappedIOError e) -> return (Left (IOError e)))
, Handler (return . Left . ParseError) , Handler (return . Left . ParseError)

View File

@ -12,19 +12,25 @@
-- --
-- Of those, the first one ('bind') is probably the most useful for the typical usecase. -- Of those, the first one ('bind') is probably the most useful for the typical usecase.
module Ldap.Client.Bind module Ldap.Client.Bind
( bind ( Password(..)
, bind
, bindEither , bindEither
, bindAsync , bindAsync
, bindAsyncSTM , bindAsyncSTM
) 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
-- | User's password.
newtype Password = Password ByteString
deriving (Show, Eq)
-- | Perform the Bind operation synchronously. Raises 'ResponseError' on failures. -- | 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 =

View File

@ -1,4 +1,3 @@
{-# LANGUAGE OverloadedStrings #-}
-- | <https://tools.ietf.org/html/rfc4511#section-4.12 Extended> operation. -- | <https://tools.ietf.org/html/rfc4511#section-4.12 Extended> operation.
-- --
-- This operation comes in four flavours: -- This operation comes in four flavours:
@ -13,10 +12,13 @@
-- --
-- Of those, the first one ('extended') is probably the most useful for the typical usecase. -- 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 Operation
Oid(..)
, extended
, extendedEither , extendedEither
, extendedAsync , extendedAsync
, extendedAsyncSTM , extendedAsyncSTM
-- ** StartTLS Operation
, startTls , startTls
, startTlsEither , startTlsEither
, startTlsAsync , startTlsAsync
@ -27,11 +29,17 @@ import Control.Monad ((<=<))
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
import Data.String (fromString)
import Data.Text (Text)
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
-- | Globally unique LDAP object identifier.
newtype Oid = Oid Text
deriving (Show, Eq)
-- | Perform the Extended operation synchronously. Raises 'ResponseError' on failures. -- | 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 =
@ -62,25 +70,31 @@ extendedRequest (Oid oid) =
Type.ExtendedRequest (Type.LdapOid oid) Type.ExtendedRequest (Type.LdapOid oid)
extendedResult :: Request -> Response -> Either ResponseError () extendedResult :: Request -> Response -> Either ResponseError ()
extendedResult req (Type.ExtendedResponse (Type.LdapResult code (Type.LdapDn (Type.LdapString dn)) extendedResult req (Type.ExtendedResponse
(Type.LdapString msg) _) _ _ :| []) (Type.LdapResult code (Type.LdapDn (Type.LdapString dn))
(Type.LdapString msg) _) _ _ :| [])
| Type.Success <- code = Right () | Type.Success <- code = Right ()
| otherwise = Left (ResponseErrorCode req code (Dn dn) msg) | otherwise = Left (ResponseErrorCode req code (Dn dn) msg)
extendedResult req res = Left (ResponseInvalid req res) extendedResult req res = Left (ResponseInvalid req res)
-- | An example of @Extended Operation@, cf. 'extended'.
startTls :: Ldap -> IO () startTls :: Ldap -> IO ()
startTls = startTls =
raise <=< startTlsEither raise <=< startTlsEither
-- | An example of @Extended Operation@, cf. 'extendedEither'.
startTlsEither :: Ldap -> IO (Either ResponseError ()) startTlsEither :: Ldap -> IO (Either ResponseError ())
startTlsEither = startTlsEither =
wait <=< startTlsAsync wait <=< startTlsAsync
-- | An example of @Extended Operation@, cf. 'extendedAsync'.
startTlsAsync :: Ldap -> IO (Async ()) startTlsAsync :: Ldap -> IO (Async ())
startTlsAsync = startTlsAsync =
atomically . startTlsAsyncSTM atomically . startTlsAsyncSTM
-- | An example of @Extended Operation@, cf. 'extendedAsyncSTM'.
startTlsAsyncSTM :: Ldap -> STM (Async ()) startTlsAsyncSTM :: Ldap -> STM (Async ())
startTlsAsyncSTM l = startTlsAsyncSTM l =
extendedAsyncSTM l (Oid "1.3.6.1.4.1.1466.20037") Nothing extendedAsyncSTM l (Oid (fromString "1.3.6.1.4.1.1466.20037"))
Nothing

View File

@ -7,13 +7,10 @@ module Ldap.Client.Internal
, ClientMessage(..) , ClientMessage(..)
, Type.ResultCode(..) , Type.ResultCode(..)
, Async , Async
, Oid(..)
, AttrList , AttrList
-- * Waiting for Request Completion -- * Waiting for Request Completion
, wait , wait
, waitSTM , waitSTM
, unbindAsync
, unbindAsyncSTM
-- * Misc -- * Misc
, Response , Response
, ResponseError(..) , ResponseError(..)
@ -21,11 +18,12 @@ module Ldap.Client.Internal
, raise , raise
, sendRequest , sendRequest
, Dn(..) , Dn(..)
, RelativeDn(..)
, Password(..)
, Attr(..) , Attr(..)
, AttrValue , AttrValue
, unAttr , unAttr
-- * Unbind operation
, unbindAsync
, unbindAsyncSTM
) where ) where
import Control.Concurrent.STM (STM, atomically) import Control.Concurrent.STM (STM, atomically)
@ -42,12 +40,15 @@ import Network (PortNumber)
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
-- | LDAP host.
data Host = data Host =
Plain String Plain String -- ^ Plain LDAP. Do not use!
| Secure String | Insecure String -- ^ LDAP over TLS without the certificate validity check.
| Insecure String -- Only use for testing!
| Secure String -- ^ LDAP over TLS. Use!
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)
-- | A token. All functions that interact with the Directory require one.
data Ldap = Ldap data Ldap = Ldap
{ client :: TQueue ClientMessage { client :: TQueue ClientMessage
} deriving (Eq) } deriving (Eq)
@ -57,35 +58,33 @@ 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.
data Async a = Async (STM (Either ResponseError a)) 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)
-- | Unique identifier of an LDAP entry.
newtype Dn = Dn Text newtype Dn = Dn Text
deriving (Show, Eq) deriving (Show, Eq)
newtype RelativeDn = RelativeDn Text -- | Response indicates a failed operation.
deriving (Show, Eq)
newtype Oid = Oid Text
deriving (Show, Eq)
newtype Password = Password ByteString
deriving (Show, Eq)
data ResponseError = data ResponseError =
ResponseInvalid Request Response ResponseInvalid Request Response -- ^ LDAP server did not follow the protocol, so @ldap-client@ couldn't make sense of the response.
| ResponseErrorCode Request Type.ResultCode Dn Text | ResponseErrorCode Request Type.ResultCode Dn Text -- ^ The response contains a result code indicating failure and an error message.
deriving (Show, Eq, Typeable) deriving (Show, Eq, Typeable)
instance Exception ResponseError instance Exception ResponseError
-- | Attribute name.
newtype Attr = Attr Text newtype Attr = Attr Text
deriving (Show, Eq) deriving (Show, Eq)
-- | Attribute value.
type AttrValue = ByteString type AttrValue = ByteString
-- | List of attributes and their values. @f@ is the structure these
-- values are in, e.g. 'NonEmpty'.
type AttrList f = [(Attr, f AttrValue)] 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
@ -93,9 +92,16 @@ type AttrList f = [(Attr, f AttrValue)]
unAttr :: Attr -> Text unAttr :: Attr -> Text
unAttr (Attr a) = a unAttr (Attr a) = a
-- | Wait for operation completion.
wait :: Async a -> IO (Either ResponseError a) wait :: Async a -> IO (Either ResponseError a)
wait = atomically . waitSTM wait = atomically . waitSTM
-- | Wait for operation completion inside 'STM'.
--
-- Do not use this inside the same 'STM' transaction the operation was
-- requested in! To give LDAP the chance to respond to it that transaction
-- should commit. After that, applying 'waitSTM' to the corresponding 'Async'
-- 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
@ -112,7 +118,9 @@ raise :: Exception e => Either e a -> IO a
raise = either throwIO return raise = either throwIO return
-- | Note that 'unbindAsync' does not return an 'Async', -- | Terminate the connection to the Directory.
--
-- Note that 'unbindAsync' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence -- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted -- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway. -- in an exception anyway.
@ -120,7 +128,9 @@ unbindAsync :: Ldap -> IO ()
unbindAsync = unbindAsync =
atomically . unbindAsyncSTM atomically . unbindAsyncSTM
-- | Note that 'unbindAsyncSTM' does not return an 'Async', -- | Terminate the connection to the Directory.
--
-- Note that 'unbindAsyncSTM' does not return an 'Async',
-- because LDAP server never responds to @UnbindRequest@s, hence -- because LDAP server never responds to @UnbindRequest@s, hence
-- a call to 'wait' on a hypothetical 'Async' would have resulted -- a call to 'wait' on a hypothetical 'Async' would have resulted
-- in an exception anyway. -- in an exception anyway.

View File

@ -20,6 +20,7 @@ module Ldap.Client.Modify
, modifyEither , modifyEither
, modifyAsync , modifyAsync
, modifyAsyncSTM , modifyAsyncSTM
, RelativeDn(..)
, modifyDn , modifyDn
, modifyDnEither , modifyDnEither
, modifyDnAsync , modifyDnAsync
@ -28,6 +29,7 @@ module Ldap.Client.Modify
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Text (Text)
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
import Ldap.Client.Internal import Ldap.Client.Internal
@ -86,6 +88,10 @@ 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)
-- | A component of 'Dn'.
newtype RelativeDn = RelativeDn Text
deriving (Show, Eq)
-- | Perform the Modify DN operation synchronously. Raises 'ResponseError' on failures. -- | 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 =

View File

@ -25,6 +25,7 @@ module Ldap.Client.Search
, size , size
, time , time
, typesOnly , typesOnly
, Type.DerefAliases(..)
, derefAliases , derefAliases
, Filter(..) , Filter(..)
, SearchEntry(..) , SearchEntry(..)
@ -148,38 +149,48 @@ searchResult req (Type.SearchResultDone (Type.LdapResult code (Type.LdapDn (Type
j (Type.AttributeValue x) = x j (Type.AttributeValue x) = x
searchResult req res = Left (ResponseInvalid req res) searchResult req res = Left (ResponseInvalid req res)
-- | Search options. Use 'Mod' to change some of those.
data Search = Search data Search = Search
{ _scope :: Type.Scope { _scope :: !Type.Scope
, _derefAliases :: Type.DerefAliases , _derefAliases :: !Type.DerefAliases
, _size :: Int32 , _size :: !Int32
, _time :: Int32 , _time :: !Int32
, _typesOnly :: Bool , _typesOnly :: !Bool
} deriving (Show, Eq) } deriving (Show, Eq)
defaultSearch :: Search defaultSearch :: Search
defaultSearch = Search defaultSearch = Search
{ _scope = Type.BaseObject { _scope = Type.WholeSubtree
, _size = 0 , _size = 0
, _time = 0 , _time = 0
, _typesOnly = False , _typesOnly = False
, _derefAliases = Type.NeverDerefAliases , _derefAliases = Type.NeverDerefAliases
} }
-- | Scope of the search (default: 'WholeSubtree').
scope :: Type.Scope -> Mod Search scope :: Type.Scope -> Mod Search
scope x = Mod (\y -> y { _scope = x }) scope x = Mod (\y -> y { _scope = x })
-- | Maximum number of entries to be returned as a result of the Search.
-- No limit if the value is @0@ (default: @0@).
size :: Int32 -> Mod Search size :: Int32 -> Mod Search
size x = Mod (\y -> y { _size = x }) size x = Mod (\y -> y { _size = x })
-- | Maximum time (in seconds) allowed for the Search. No limit if the value
-- is @0@ (default: @0@).
time :: Int32 -> Mod Search time :: Int32 -> Mod Search
time x = Mod (\y -> y { _time = x }) time x = Mod (\y -> y { _time = x })
-- | Whether Search results are to contain just attribute descriptions, or
-- both attribute descriptions and values (default: 'False').
typesOnly :: Bool -> Mod Search typesOnly :: Bool -> Mod Search
typesOnly x = Mod (\y -> y { _typesOnly = x }) typesOnly x = Mod (\y -> y { _typesOnly = x })
-- | Alias dereference policy (default: 'NeverDerefAliases').
derefAliases :: Type.DerefAliases -> Mod Search derefAliases :: Type.DerefAliases -> Mod Search
derefAliases x = Mod (\y -> y { _derefAliases = x }) derefAliases x = Mod (\y -> y { _derefAliases = x })
-- | Search modifier. Combine using 'Semigroup' and/or 'Monoid' instance.
newtype Mod a = Mod (a -> a) newtype Mod a = Mod (a -> a)
instance Semigroup (Mod a) where instance Semigroup (Mod a) where
@ -189,17 +200,21 @@ instance Monoid (Mod a) where
mempty = Mod id mempty = Mod id
mappend = (<>) mappend = (<>)
-- | Conditions that must be fulfilled in order for the Search to match a given entry.
data Filter = data Filter =
Not Filter Not !Filter -- ^ Filter does not match the entry
| And (NonEmpty Filter) | And !(NonEmpty Filter) -- ^ All filters match the entry
| Or (NonEmpty Filter) | Or !(NonEmpty Filter) -- ^ Any filter matches the entry
| Present Attr | Present !Attr -- ^ Attribute is present in the entry
| Attr := AttrValue | !Attr := !AttrValue -- ^ Attribute's value is equal to the assertion
| Attr :>= AttrValue | !Attr :>= !AttrValue -- ^ Attribute's value is equal to or greater than the assertion
| Attr :<= AttrValue | !Attr :<= !AttrValue -- ^ Attribute's value is equal to or less than the assertion
| Attr :~= AttrValue | !Attr :~= !AttrValue -- ^ Attribute's value approximately matches the assertion
| Attr :=* (Maybe AttrValue, [AttrValue], Maybe AttrValue) | !Attr :=* !(Maybe AttrValue, [AttrValue], Maybe AttrValue)
-- ^ Glob match
| (Maybe Attr, Maybe Attr, Bool) ::= AttrValue | (Maybe Attr, Maybe Attr, Bool) ::= AttrValue
-- ^ Extensible match
data SearchEntry = SearchEntry Dn (AttrList []) -- | Entry found during the Search.
data SearchEntry = SearchEntry !Dn !(AttrList [])
deriving (Show, Eq) deriving (Show, Eq)

View File

@ -2,10 +2,9 @@
module Ldap.Client.AddSpec (spec) where module Ldap.Client.AddSpec (spec) where
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid ((<>))
import Test.Hspec import Test.Hspec
import Ldap.Client (Dn(..), Filter(..), Scope(..), Attr(..)) import Ldap.Client (Dn(..), Filter(..), Attr(..))
import qualified Ldap.Client as Ldap import qualified Ldap.Client as Ldap
import SpecHelper (locally , dns , vulpix) import SpecHelper (locally , dns , vulpix)
@ -13,10 +12,7 @@ import SpecHelper (locally , dns , vulpix)
spec :: Spec spec :: Spec
spec = do spec = do
let go l f = Ldap.search l (Dn "o=localhost") let go l f = Ldap.search l (Dn "o=localhost") (Ldap.typesOnly True) f []
(Ldap.scope WholeSubtree <> Ldap.typesOnly True)
f
[]
it "adds an entry" $ do it "adds an entry" $ do
res <- locally $ \l -> do res <- locally $ \l -> do

View File

@ -1,6 +1,10 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Ldap.Client.BindSpec (spec) where module Ldap.Client.BindSpec (spec) where
#if __GLASGOW_HASKELL__ < 710
import Data.Monoid (mempty)
#endif
import Test.Hspec import Test.Hspec
import qualified Ldap.Asn1.Type as Ldap.Type import qualified Ldap.Asn1.Type as Ldap.Type
import Ldap.Client as Ldap import Ldap.Client as Ldap
@ -32,9 +36,6 @@ spec = 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 _]
<- Ldap.search l (Dn "o=localhost") <- Ldap.search l (Dn "o=localhost") mempty (Attr "cn" := "pikachu") []
(scope WholeSubtree)
(Attr "cn" := "pikachu")
[]
Ldap.bind l udn (Password "i-choose-you") Ldap.bind l udn (Password "i-choose-you")
res `shouldBe` Right () res `shouldBe` Right ()

View File

@ -1,10 +1,9 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Ldap.Client.DeleteSpec (spec) where module Ldap.Client.DeleteSpec (spec) where
import Data.Monoid ((<>))
import Test.Hspec import Test.Hspec
import Ldap.Client (Dn(..), Filter(..), Scope(..), Attr(..)) import Ldap.Client (Dn(..), Filter(..), Attr(..))
import qualified Ldap.Client as Ldap import qualified Ldap.Client as Ldap
import qualified Ldap.Asn1.Type as Ldap.Type import qualified Ldap.Asn1.Type as Ldap.Type
@ -13,10 +12,7 @@ import SpecHelper (locally, dns, pikachu, oddish)
spec :: Spec spec :: Spec
spec = do spec = do
let go l f = Ldap.search l (Dn "o=localhost") let go l f = Ldap.search l (Dn "o=localhost") (Ldap.typesOnly True) f []
(Ldap.scope WholeSubtree <> Ldap.typesOnly True)
f
[]
it "deletes an entry" $ do it "deletes an entry" $ do
res <- locally $ \l -> do res <- locally $ \l -> do

View File

@ -1,7 +1,6 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Ldap.Client.ModifySpec (spec) where module Ldap.Client.ModifySpec (spec) where
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
import Ldap.Client as Ldap import Ldap.Client as Ldap
@ -11,10 +10,7 @@ import SpecHelper (locally, charizard, pikachu, raichu)
spec :: Spec spec :: Spec
spec = do spec = do
let go l f = Ldap.search l (Dn "o=localhost") let go l f = Ldap.search l (Dn "o=localhost") (Ldap.typesOnly True) f []
(Ldap.scope WholeSubtree <> Ldap.typesOnly True)
f
[]
context "delete" $ do context "delete" $ do
it "can land charizard" $ do it "can land charizard" $ do

View File

@ -2,7 +2,6 @@
module Ldap.Client.SearchSpec (spec) where module Ldap.Client.SearchSpec (spec) where
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid ((<>))
import Test.Hspec import Test.Hspec
import Ldap.Client as Ldap import Ldap.Client as Ldap
import qualified Ldap.Asn1.Type as Ldap.Type import qualified Ldap.Asn1.Type as Ldap.Type
@ -28,10 +27,7 @@ import SpecHelper
spec :: Spec spec :: Spec
spec = do spec = do
let go l f = Ldap.search l (Dn "o=localhost") let go l f = Ldap.search l (Dn "o=localhost") (Ldap.typesOnly True) f []
(Ldap.scope WholeSubtree <> Ldap.typesOnly True)
f
[]
it "cannot search as pikachu" $ do it "cannot search as pikachu" $ do
res <- locally $ \l -> do res <- locally $ \l -> do