implement SASL EXTERNAL authentication (tested with OpenLDAP and client-side certificates as the external auth)

This commit is contained in:
Matthias Hörmann 2017-01-17 22:03:23 +01:00
parent cbeafaf99a
commit 9921b3178e
4 changed files with 61 additions and 3 deletions

View File

@ -15,6 +15,7 @@ import Data.Foldable (fold, foldMap)
import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty (NonEmpty)
import Data.Maybe (maybe) import Data.Maybe (maybe)
import Data.Monoid (Endo(Endo), (<>), mempty) import Data.Monoid (Endo(Endo), (<>), mempty)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text import qualified Data.Text.Encoding as Text
import Prelude (Integer, (.), fromIntegral) import Prelude (Integer, (.), fromIntegral)
@ -309,12 +310,22 @@ instance ToAsn1 ProtocolClientOp where
@ @
AuthenticationChoice ::= CHOICE { AuthenticationChoice ::= CHOICE {
simple [0] OCTET STRING, simple [0] OCTET STRING,
sasl [3] SaslCredentials,
... } ... }
SaslCredentials ::= SEQUENCE {
mechanism LDAPString,
credentials OCTET STRING OPTIONAL }
@ @
-} -}
instance ToAsn1 AuthenticationChoice where instance ToAsn1 AuthenticationChoice where
toAsn1 (Simple s) = other Asn1.Context 0 s toAsn1 (Simple s) = other Asn1.Context 0 s
toAsn1 (Sasl External c) =
context 3 (fold
[ toAsn1 (LdapString (Text.pack "EXTERNAL"))
, maybe mempty (toAsn1 . LdapString) c
])
{- | {- |
@ @
AttributeSelection ::= SEQUENCE OF selector LDAPString AttributeSelection ::= SEQUENCE OF selector LDAPString

View File

@ -48,7 +48,14 @@ data ProtocolServerOp =
deriving (Show, Eq) deriving (Show, Eq)
-- | Not really a choice until SASL is supported. -- | Not really a choice until SASL is supported.
newtype AuthenticationChoice = Simple ByteString data AuthenticationChoice =
Simple ByteString
| Sasl !SaslMechanism !(Maybe Text)
deriving (Show, Eq)
-- | SASL Mechanism, for now only SASL EXTERNAL is supported
data SaslMechanism =
External
deriving (Show, Eq) deriving (Show, Eq)
-- | Scope of the search to be performed. -- | Scope of the search to be performed.

View File

@ -18,6 +18,7 @@ module Ldap.Client
-- * Bind -- * Bind
, Password(..) , Password(..)
, bind , bind
, externalBind
-- * Search -- * Search
, search , search
, SearchEntry(..) , SearchEntry(..)
@ -90,7 +91,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 (Password(..), bind) import Ldap.Client.Bind (Password(..), bind, externalBind)
import Ldap.Client.Search import Ldap.Client.Search
( search ( search
, Search , Search

View File

@ -17,6 +17,10 @@ module Ldap.Client.Bind
, bindEither , bindEither
, bindAsync , bindAsync
, bindAsyncSTM , bindAsyncSTM
, externalBind
, externalBindEither
, externalBindAsync
, externalBindAsyncSTM
, Async , Async
, wait , wait
, waitSTM , waitSTM
@ -24,6 +28,7 @@ module Ldap.Client.Bind
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Ldap.Asn1.Type as Type import qualified Ldap.Asn1.Type as Type
@ -73,3 +78,37 @@ 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)
-- | Perform a SASL EXTERNAL Bind operation synchronously. Raises 'ResponseError' on failures.
externalBind :: Ldap -> Dn -> Maybe Text -> IO ()
externalBind l username mCredentials =
raise =<< externalBindEither l username mCredentials
-- | Perform a SASL EXTERNAL Bind operation synchronously. Returns @Left e@ where
-- @e@ is a 'ResponseError' on failures.
externalBindEither :: Ldap -> Dn -> Maybe Text -> IO (Either ResponseError ())
externalBindEither l username mCredentials =
wait =<< externalBindAsync l username mCredentials
-- | Perform the SASL EXTERNAL Bind operation asynchronously. Call 'Ldap.Client.wait' to wait
-- for its completion.
externalBindAsync :: Ldap -> Dn -> Maybe Text -> IO (Async ())
externalBindAsync l username mCredentials =
atomically (externalBindAsyncSTM l username mCredentials)
-- | Perform the SASL EXTERNAL Bind operation asynchronously.
--
-- Don't wait for its completion (with 'Ldap.Client.waitSTM') in the
-- same transaction you've performed it in.
externalBindAsyncSTM :: Ldap -> Dn -> Maybe Text -> STM (Async ())
externalBindAsyncSTM l username mCredentials =
let req = externalBindRequest username mCredentials in sendRequest l (bindResult req) req
externalBindRequest :: Dn -> Maybe Text -> Request
externalBindRequest (Dn username) mCredentials =
Type.BindRequest ldapVersion
(Type.LdapDn (Type.LdapString username))
(Type.Sasl Type.External mCredentials)
where
ldapVersion = 3