Support ModifyDN

This commit is contained in:
Matvey Aksenov 2015-04-03 22:27:35 +00:00
parent ebccd8628f
commit dd1a89d426
15 changed files with 128 additions and 39 deletions

View File

@ -15,7 +15,7 @@ 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 |
Compare Operation | 4.10 | ✔ Compare Operation | 4.10 | ✔
Abandon Operation | 4.11 | ✘ Abandon Operation | 4.11 | ✘
Extended Operation | 4.12 | ✔ Extended Operation | 4.12 | ✔

View File

@ -21,6 +21,9 @@ import qualified Data.Text.Encoding as Text
import Ldap.Asn1.Type import Ldap.Asn1.Type
{-# ANN module "HLint: ignore Use const" #-}
{-# ANN module "HLint: ignore Avoid lambda" #-}
class FromAsn1 a where class FromAsn1 a where
fromAsn1 :: Parser [ASN1] a fromAsn1 :: Parser [ASN1] a
@ -262,6 +265,7 @@ instance FromAsn1 ProtocolServerOp where
, fmap ModifyResponse (app 7) , fmap ModifyResponse (app 7)
, fmap AddResponse (app 9) , fmap AddResponse (app 9)
, fmap DeleteResponse (app 11) , fmap DeleteResponse (app 11)
, fmap ModifyDnResponse (app 13)
, fmap CompareResponse (app 15) , fmap CompareResponse (app 15)
, do , do
Asn1.Start (Asn1.Container Asn1.Application 24) <- next Asn1.Start (Asn1.Container Asn1.Application 24) <- next

View File

@ -61,6 +61,12 @@ LDAPDN ::= LDAPString -- Constrained to <distinguishedName>
instance ToAsn1 LdapDn where instance ToAsn1 LdapDn where
toAsn1 (LdapDn s) = toAsn1 s toAsn1 (LdapDn s) = toAsn1 s
{- |
RelativeLDAPDN ::= LDAPString -- Constrained to <name-component>
-}
instance ToAsn1 RelativeLdapDn where
toAsn1 (RelativeLdapDn s) = toAsn1 s
{- | {- |
AttributeDescription ::= LDAPString AttributeDescription ::= LDAPString
-} -}
@ -172,6 +178,12 @@ AddRequest ::= [APPLICATION 8] SEQUENCE {
DelRequest ::= [APPLICATION 10] LDAPDN DelRequest ::= [APPLICATION 10] LDAPDN
ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
entry LDAPDN,
newrdn RelativeLDAPDN,
deleteoldrdn BOOLEAN,
newSuperior [0] LDAPDN OPTIONAL }
CompareRequest ::= [APPLICATION 14] SEQUENCE { CompareRequest ::= [APPLICATION 14] SEQUENCE {
entry LDAPDN, entry LDAPDN,
ava AttributeValueAssertion } ava AttributeValueAssertion }
@ -219,6 +231,15 @@ instance ToAsn1 ProtocolClientOp where
application 8 (toAsn1 dn <> toAsn1 as) application 8 (toAsn1 dn <> toAsn1 as)
toAsn1 (DeleteRequest (LdapDn (LdapString dn))) = toAsn1 (DeleteRequest (LdapDn (LdapString dn))) =
other Asn1.Application 10 (Text.encodeUtf8 dn) other Asn1.Application 10 (Text.encodeUtf8 dn)
toAsn1 (ModifyDnRequest dn rdn del new) =
application 12 (fold
[ toAsn1 dn
, toAsn1 rdn
, single (Asn1.Boolean del)
, maybe mempty
(\(LdapDn (LdapString dn')) -> other Asn1.Context 0 (Text.encodeUtf8 dn'))
new
])
toAsn1 (CompareRequest dn av) = toAsn1 (CompareRequest dn av) =
application 14 (toAsn1 dn <> sequence (toAsn1 av)) application 14 (toAsn1 dn <> sequence (toAsn1 av))
toAsn1 (ExtendedRequest (LdapOid oid) mv) = toAsn1 (ExtendedRequest (LdapOid oid) mv) =

View File

@ -19,10 +19,11 @@ 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)]
| AddRequest LdapDn AttributeList | AddRequest LdapDn AttributeList
| DeleteRequest LdapDn | DeleteRequest LdapDn
| ModifyDnRequest LdapDn RelativeLdapDn Bool (Maybe LdapDn)
| CompareRequest LdapDn AttributeValueAssertion | CompareRequest LdapDn AttributeValueAssertion
| ModifyRequest LdapDn [(Operation, PartialAttribute)]
| ExtendedRequest LdapOid (Maybe ByteString) | ExtendedRequest LdapOid (Maybe ByteString)
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)
@ -34,6 +35,7 @@ data ProtocolServerOp =
| ModifyResponse LdapResult | ModifyResponse LdapResult
| AddResponse LdapResult | AddResponse LdapResult
| DeleteResponse LdapResult | DeleteResponse LdapResult
| ModifyDnResponse LdapResult
| CompareResponse LdapResult | CompareResponse LdapResult
| ExtendedResponse LdapResult (Maybe LdapOid) (Maybe ByteString) | ExtendedResponse LdapResult (Maybe LdapOid) (Maybe ByteString)
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)
@ -163,6 +165,9 @@ data PartialAttribute = PartialAttribute AttributeDescription [AttributeValue]
newtype LdapDn = LdapDn LdapString newtype LdapDn = LdapDn LdapString
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)
newtype RelativeLdapDn = RelativeLdapDn LdapString
deriving (Show, Eq, Ord)
newtype ReferralUris = ReferralUris (NonEmpty Uri) newtype ReferralUris = ReferralUris (NonEmpty Uri)
deriving (Show, Eq, Ord) deriving (Show, Eq, Ord)

View File

@ -1,6 +1,5 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Ldap.Client module Ldap.Client
( Host(..) ( Host(..)
@ -35,6 +34,9 @@ module Ldap.Client
, add , add
-- * Delete Operation -- * Delete Operation
, delete , delete
-- * ModifyDn Operation
, RelativeDn(..)
, modifyDn
-- * Compare Operation -- * Compare Operation
, compare , compare
-- * Extended Operation -- * Extended Operation
@ -85,7 +87,7 @@ import Ldap.Client.Search
, Filter(..) , Filter(..)
, SearchEntry(..) , SearchEntry(..)
) )
import Ldap.Client.Modify (Operation(..), modify) import Ldap.Client.Modify (Operation(..), modify, 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)
@ -177,37 +179,22 @@ 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, Map.empty, 1) $ \loop (!got, !results, !counter) -> do flip fix (Map.empty, Map.empty, 1) $ \loop (!got, !results, !counter) ->
loop =<< atomically (asum loop =<< atomically (asum
[ do New new var <- readTQueue client [ do New new var <- readTQueue client
writeTQueue outq (Type.LdapMessage (Type.Id counter) new Nothing) writeTQueue outq (Type.LdapMessage (Type.Id counter) new Nothing)
return (got, Map.insert (Type.Id counter) var results, counter + 1) return (got, Map.insert (Type.Id counter) var results, counter + 1)
, do Type.LdapMessage mid op _ <- readTQueue inq , do Type.LdapMessage mid op _ <- readTQueue inq
case op of case op of
Type.BindResponse {} -> do Type.SearchResultEntry {} ->
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
return (Map.delete mid got, Map.delete mid results, counter)
Type.SearchResultEntry {} -> do
return (Map.insertWith (++) mid [op] got, results, counter) return (Map.insertWith (++) mid [op] got, results, counter)
Type.SearchResultReference {} -> do Type.SearchResultReference {} ->
return (got, results, counter) return (got, results, counter)
Type.SearchResultDone {} -> do Type.SearchResultDone {} -> do
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 _ -> do
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
return (Map.delete mid got, Map.delete mid results, counter)
Type.AddResponse {} -> do
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
return (Map.delete mid got, Map.delete mid results, counter)
Type.DeleteResponse {} -> do
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
return (Map.delete mid got, Map.delete mid results, counter)
Type.CompareResponse {} -> do
traverse_ (\var -> putTMVar var (op :| [])) (Map.lookup mid results)
return (Map.delete mid got, Map.delete mid results, counter)
Type.ExtendedResponse {} -> 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)
]) ])

View File

@ -36,8 +36,8 @@ 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
extendedRequest :: Oid -> Maybe ByteString -> Request extendedRequest :: Oid -> Maybe ByteString -> Request
extendedRequest (Oid oid) mv = extendedRequest (Oid oid) =
Type.ExtendedRequest (Type.LdapOid oid) mv 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.LdapResult code (Type.LdapDn (Type.LdapString dn))

View File

@ -1,4 +1,3 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Ldap.Client.Internal module Ldap.Client.Internal
@ -20,6 +19,7 @@ module Ldap.Client.Internal
, raise , raise
, sendRequest , sendRequest
, Dn(..) , Dn(..)
, RelativeDn(..)
, Password(..) , Password(..)
, Attr(..) , Attr(..)
, unAttr , unAttr
@ -62,6 +62,9 @@ instance Functor Async where
newtype Dn = Dn Text newtype Dn = Dn Text
deriving (Show, Eq) deriving (Show, Eq)
newtype RelativeDn = RelativeDn Text
deriving (Show, Eq)
newtype Oid = Oid ByteString newtype Oid = Oid ByteString
deriving (Show, Eq) deriving (Show, Eq)

View File

@ -4,6 +4,10 @@ module Ldap.Client.Modify
, modifyEither , modifyEither
, modifyAsync , modifyAsync
, modifyAsyncSTM , modifyAsyncSTM
, modifyDn
, modifyDnEither
, modifyDnAsync
, modifyDnAsyncSTM
) where ) where
import Control.Monad.STM (STM, atomically) import Control.Monad.STM (STM, atomically)
@ -55,3 +59,33 @@ modifyResult req (Type.ModifyResponse (Type.LdapResult code (Type.LdapDn (Type.L
| Type.Success <- code = Right () | Type.Success <- code = Right ()
| otherwise = Left (ResponseErrorCode req code (Dn dn) msg) | otherwise = Left (ResponseErrorCode req code (Dn dn) msg)
modifyResult req res = Left (ResponseInvalid req res) modifyResult req res = Left (ResponseInvalid req res)
modifyDn :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO ()
modifyDn l dn rdn del new =
raise =<< modifyDnEither l dn rdn del new
modifyDnEither :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Either ResponseError ())
modifyDnEither l dn rdn del new =
wait =<< modifyDnAsync l dn rdn del new
modifyDnAsync :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> IO (Async ())
modifyDnAsync l dn rdn del new =
atomically (modifyDnAsyncSTM l dn rdn del new)
modifyDnAsyncSTM :: Ldap -> Dn -> RelativeDn -> Bool -> Maybe Dn -> STM (Async ())
modifyDnAsyncSTM l dn rdn del new =
let req = modifyDnRequest dn rdn del new in sendRequest l (modifyDnResult req) req
modifyDnRequest :: Dn -> RelativeDn -> Bool -> Maybe Dn -> Request
modifyDnRequest (Dn dn) (RelativeDn rdn) del new =
Type.ModifyDnRequest (Type.LdapDn (Type.LdapString dn))
(Type.RelativeLdapDn (Type.LdapString rdn))
del
(fmap (\(Dn dn') -> Type.LdapDn (Type.LdapString dn')) new)
modifyDnResult :: Request -> Response -> Either ResponseError ()
modifyDnResult req (Type.ModifyDnResponse (Type.LdapResult code (Type.LdapDn (Type.LdapString dn)) (Type.LdapString msg) _) :| [])
| Type.Success <- code = Right ()
| otherwise = Left (ResponseErrorCode req code (Dn dn) msg)
modifyDnResult req res = Left (ResponseInvalid req res)

View File

@ -21,9 +21,9 @@ spec = do
it "adds an entry" $ do it "adds an entry" $ do
res <- locally $ \l -> do res <- locally $ \l -> do
Ldap.add l vulpix Ldap.add l vulpix
[ (Attr "cn", (NonEmpty.fromList ["vulpix"])) [ (Attr "cn", NonEmpty.fromList ["vulpix"])
, (Attr "evolution", (NonEmpty.fromList ["0"])) , (Attr "evolution", NonEmpty.fromList ["0"])
, (Attr "type", (NonEmpty.fromList ["fire"])) , (Attr "type", NonEmpty.fromList ["fire"])
] ]
res <- go l (Attr "cn" := "vulpix") res <- go l (Attr "cn" := "vulpix")
dns res `shouldBe` [vulpix] dns res `shouldBe` [vulpix]

View File

@ -11,12 +11,12 @@ 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 ->
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 ->
Ldap.bind l (Dn "cn=admin") (Password "public") Ldap.bind l (Dn "cn=admin") (Password "public")
res `shouldBe` Left res `shouldBe` Left
(Ldap.ResponseError (Ldap.ResponseError
@ -31,7 +31,7 @@ spec = do
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 _]
<- Ldap.search l (Dn "o=localhost") <- Ldap.search l (Dn "o=localhost")
(scope WholeSubtree) (scope WholeSubtree)
(Attr "cn" := "pikachu") (Attr "cn" := "pikachu")

View File

@ -26,7 +26,7 @@ spec = do
res `shouldBe` Right () res `shouldBe` Right ()
it "tries to delete an non-existing entry, unsuccessfully" $ do it "tries to delete an non-existing entry, unsuccessfully" $ do
res <- locally $ \l -> do res <- locally $ \l ->
Ldap.delete l oddish Ldap.delete l oddish
res `shouldBe` Left res `shouldBe` Left
(Ldap.ResponseError (Ldap.ResponseError

View File

@ -21,7 +21,7 @@ spec = do
(Dn "") (Dn "")
"0 not supported")) "0 not supported"))
it "sends a startTLS request" $ do it "sends a StartTLS request" $ do
res <- locally $ \l -> res <- locally $ \l ->
Ldap.startTls l Ldap.startTls l
res `shouldBe` Left res `shouldBe` Left

View File

@ -7,7 +7,7 @@ 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
import SpecHelper (locally, charizard, pikachu) import SpecHelper (locally, charizard, pikachu, raichu)
spec :: Spec spec :: Spec
@ -30,7 +30,7 @@ spec = do
res `shouldBe` Right () res `shouldBe` Right ()
it "tries to remove pikachu's password, unsuccessfully" $ do it "tries to remove pikachu's password, unsuccessfully" $ do
res <- locally $ \l -> do res <- locally $ \l ->
Ldap.modify l pikachu [Attr "password" `Delete` []] Ldap.modify l pikachu [Attr "password" `Delete` []]
res `shouldBe` Left res `shouldBe` Left
(ResponseError (ResponseError
@ -45,7 +45,7 @@ spec = do
(Dn "o=localhost") (Dn "o=localhost")
"cannot delete password")) "cannot delete password"))
context "add" $ do context "add" $
it "can feed charizard" $ do it "can feed charizard" $ do
res <- locally $ \l -> do res <- locally $ \l -> do
[x] <- go l (Attr "cn" := "charizard") [x] <- go l (Attr "cn" := "charizard")
@ -57,7 +57,7 @@ spec = do
lookupAttr (Attr "type") y `shouldBe` Just ["fire", "flying", "fed"] lookupAttr (Attr "type") y `shouldBe` Just ["fire", "flying", "fed"]
res `shouldBe` Right () res `shouldBe` Right ()
context "replace" $ do context "replace" $
it "can put charizard to sleep" $ do it "can put charizard to sleep" $ do
res <- locally $ \l -> do res <- locally $ \l -> do
[x] <- go l (Attr "cn" := "charizard") [x] <- go l (Attr "cn" := "charizard")
@ -69,5 +69,23 @@ spec = do
lookupAttr (Attr "type") y `shouldBe` Just ["sleeping"] lookupAttr (Attr "type") y `shouldBe` Just ["sleeping"]
res `shouldBe` Right () res `shouldBe` Right ()
context "modify dn" $
it "evolves pikachu into raichu" $ do
res <- locally $ \l -> do
[] <- go l (Attr "cn" := "raichu")
Ldap.modifyDn l pikachu (RelativeDn "cn=raichu") False Nothing
Ldap.modify l raichu [Attr "evolution" `Replace` ["1"]]
[res] <- go l (Attr "cn" := "raichu")
res `shouldBe`
SearchEntry raichu
[ (Attr "cn", ["raichu"])
, (Attr "evolution", ["1"])
, (Attr "type", ["electric"])
, (Attr "password", ["i-choose-you"])
]
res `shouldBe` Right ()
lookupAttr :: Attr -> SearchEntry -> Maybe [ByteString] lookupAttr :: Attr -> SearchEntry -> Maybe [ByteString]
lookupAttr a (SearchEntry _ as) = lookup a as lookupAttr a (SearchEntry _ as) = lookup a as

View File

@ -17,6 +17,7 @@ module SpecHelper
, metapod , metapod
, butterfree , butterfree
, pikachu , pikachu
, raichu
, vulpix , vulpix
, oddish , oddish
) where ) where
@ -90,6 +91,9 @@ butterfree = Dn "cn=butterfree,o=localhost"
pikachu :: Dn pikachu :: Dn
pikachu = Dn "cn=pikachu,o=localhost" pikachu = Dn "cn=pikachu,o=localhost"
raichu :: Dn
raichu = Dn "cn=raichu,o=localhost"
vulpix :: Dn vulpix :: Dn
vulpix = Dn "cn=vulpix,o=localhost" vulpix = Dn "cn=vulpix,o=localhost"

View File

@ -158,6 +158,19 @@ server.del('o=localhost', [], function(req, res, next) {
return next(new ldapjs.NoSuchObjectError(req.dn.toString())); return next(new ldapjs.NoSuchObjectError(req.dn.toString()));
}); });
server.modifyDN('o=localhost', [], function(req, res, next) {
for (var i = 0; i < pokemon.length; i++) {
if (req.dn.toString() === pokemon[i].dn) {
req.dn.rdns[0] = req.newRdn.rdns[0];
pokemon[i].dn = req.dn.toString();
pokemon[i].attributes.cn = req.newRdn.rdns[0].cn;
}
}
res.end();
return next();
});
server.compare('o=localhost', [], function(req, res, next) { server.compare('o=localhost', [], function(req, res, next) {
for (var i = 0; i < pokemon.length; i++) { for (var i = 0; i < pokemon.length; i++) {
if (req.dn.toString() === pokemon[i].dn) { if (req.dn.toString() === pokemon[i].dn) {
@ -183,6 +196,6 @@ server.compare('o=localhost', [], function(req, res, next) {
return next(new ldapjs.NoSuchObjectError(req.dn.toString())); return next(new ldapjs.NoSuchObjectError(req.dn.toString()));
}); });
server.listen(port, '0.0.0.0', function() { server.listen(port, function() {
console.log("ldaps://localhost:%d", port); console.log("ldaps://localhost:%d", port);
}); });