diff --git a/README.md b/README.md index a26ed2d8..00e8b4c1 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Import the latest version with: - Modify Requests / Responses - Add Requests / Responses - Delete Requests / Responses + - Modify DN Requests / Responses ## Examples: diff --git a/client.go b/client.go index 055b27b5..c7f41f6f 100644 --- a/client.go +++ b/client.go @@ -18,6 +18,7 @@ type Client interface { Add(addRequest *AddRequest) error Del(delRequest *DelRequest) error Modify(modifyRequest *ModifyRequest) error + ModifyDN(modifyDNRequest *ModifyDNRequest) error Compare(dn, attribute, value string) (bool, error) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) diff --git a/moddn.go b/moddn.go new file mode 100644 index 00000000..7065b814 --- /dev/null +++ b/moddn.go @@ -0,0 +1,104 @@ +// Package ldap - moddn.go contains ModifyDN functionality +// +// https://tools.ietf.org/html/rfc4511 +// ModifyDNRequest ::= [APPLICATION 12] SEQUENCE { +// entry LDAPDN, +// newrdn RelativeLDAPDN, +// deleteoldrdn BOOLEAN, +// newSuperior [0] LDAPDN OPTIONAL } +// +// +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +// ModifyDNRequest holds the request to modify a DN +type ModifyDNRequest struct { + DN string + NewRDN string + DeleteOldRDN bool + NewSuperior string +} + +// NewModifyDNRequest creates a new request which can be passed to ModifyDN(). +// +// To move an object in the tree, set the "newSup" to the new parent entry DN. Use an +// empty string for just changing the object's RDN. +// +// For moving the object without renaming, the "rdn" must be the first +// RDN of the given DN. +// +// A call like +// mdnReq := NewModifyDNRequest("uid=someone,dc=example,dc=org", "uid=newname", true, "") +// will setup the request to just rename uid=someone,dc=example,dc=org to +// uid=newname,dc=example,dc=org. +func NewModifyDNRequest(dn string, rdn string, delOld bool, newSup string) *ModifyDNRequest { + return &ModifyDNRequest{ + DN: dn, + NewRDN: rdn, + DeleteOldRDN: delOld, + NewSuperior: newSup, + } +} + +func (m ModifyDNRequest) encode() *ber.Packet { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyDNRequest, nil, "Modify DN Request") + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.DN, "DN")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.NewRDN, "New RDN")) + request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, m.DeleteOldRDN, "Delete old RDN")) + if m.NewSuperior != "" { + request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, m.NewSuperior, "New Superior")) + } + return request +} + +// ModifyDN renames the given DN and optionally move to another base (when the "newSup" argument +// to NewModifyDNRequest() is not ""). +func (l *Conn) ModifyDN(m *ModifyDNRequest) error { + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID")) + packet.AppendChild(m.encode()) + + l.Debug.PrintPacket(packet) + + msgCtx, err := l.sendMessage(packet) + if err != nil { + return err + } + defer l.finishMessage(msgCtx) + + l.Debug.Printf("%d: waiting for response", msgCtx.id) + packetResponse, ok := <-msgCtx.responses + if !ok { + return NewError(ErrorNetwork, errors.New("ldap: channel closed")) + } + packet, err = packetResponse.ReadPacket() + l.Debug.Printf("%d: got response %p", msgCtx.id, packet) + if err != nil { + return err + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationModifyDNResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return NewError(resultCode, errors.New(resultDescription)) + } + } else { + log.Printf("Unexpected Response: %d", packet.Children[1].Tag) + } + + l.Debug.Printf("%d: returning", msgCtx.id) + return nil +} diff --git a/moddn_test.go b/moddn_test.go new file mode 100644 index 00000000..f4af41aa --- /dev/null +++ b/moddn_test.go @@ -0,0 +1,75 @@ +package ldap_test + +import ( + "log" + + "gopkg.in/ldap.v2" +) + +// ExampleConn_ModifyDN_renameNoMove shows how to rename an entry without moving it +func ExampleConn_ModifyDN_renameNoMove() { + conn, err := ldap.Dial("tcp", "ldap.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&ldap.SimpleBindRequest{ + Username: "uid=someone,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + if err != nil { + log.Fatalf("Failed to bind: %s\n", err) + } + // just rename to uid=new,ou=people,dc=example,dc=org: + req := ldap.NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "") + if err = conn.ModifyDN(req); err != nil { + log.Fatalf("Failed to call ModifyDN(): %s\n", err) + } +} + +// ExampleConn_ModifyDN_renameAndMove shows how to rename an entry and moving it to a new base +func ExampleConn_ModifyDN_renameAndMove() { + conn, err := ldap.Dial("tcp", "ldap.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&ldap.SimpleBindRequest{ + Username: "uid=someone,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + if err != nil { + log.Fatalf("Failed to bind: %s\n", err) + } + // rename to uid=new,ou=people,dc=example,dc=org and move to ou=users,dc=example,dc=org -> + // uid=new,ou=users,dc=example,dc=org + req := ldap.NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=new", true, "ou=users,dc=example,dc=org") + + if err = conn.ModifyDN(req); err != nil { + log.Fatalf("Failed to call ModifyDN(): %s\n", err) + } +} + +// ExampleConn_ModifyDN_moveOnly shows how to move an entry to a new base without renaming the RDN +func ExampleConn_ModifyDN_moveOnly() { + conn, err := ldap.Dial("tcp", "ldap.example.org:389") + if err != nil { + log.Fatalf("Failed to connect: %s\n", err) + } + defer conn.Close() + + _, err = conn.SimpleBind(&ldap.SimpleBindRequest{ + Username: "uid=someone,ou=people,dc=example,dc=org", + Password: "MySecretPass", + }) + if err != nil { + log.Fatalf("Failed to bind: %s\n", err) + } + // move to ou=users,dc=example,dc=org -> uid=user,ou=users,dc=example,dc=org + req := ldap.NewModifyDNRequest("uid=user,ou=people,dc=example,dc=org", "uid=user", true, "ou=users,dc=example,dc=org") + if err = conn.ModifyDN(req); err != nil { + log.Fatalf("Failed to call ModifyDN(): %s\n", err) + } +}