1// File contains Modify functionality
2//
3// https://tools.ietf.org/html/rfc4511
4//
5// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
6//      object          LDAPDN,
7//      changes         SEQUENCE OF change SEQUENCE {
8//           operation       ENUMERATED {
9//                add     (0),
10//                delete  (1),
11//                replace (2),
12//                ...  },
13//           modification    PartialAttribute } }
14//
15// PartialAttribute ::= SEQUENCE {
16//      type       AttributeDescription,
17//      vals       SET OF value AttributeValue }
18//
19// AttributeDescription ::= LDAPString
20//                         -- Constrained to <attributedescription>
21//                         -- [RFC4512]
22//
23// AttributeValue ::= OCTET STRING
24//
25
26package ldap
27
28import (
29	"log"
30
31	ber "github.com/go-asn1-ber/asn1-ber"
32)
33
34// Change operation choices
35const (
36	AddAttribute     = 0
37	DeleteAttribute  = 1
38	ReplaceAttribute = 2
39)
40
41// PartialAttribute for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
42type PartialAttribute struct {
43	// Type is the type of the partial attribute
44	Type string
45	// Vals are the values of the partial attribute
46	Vals []string
47}
48
49func (p *PartialAttribute) encode() *ber.Packet {
50	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
51	seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.Type, "Type"))
52	set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
53	for _, value := range p.Vals {
54		set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
55	}
56	seq.AppendChild(set)
57	return seq
58}
59
60// Change for a ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
61type Change struct {
62	// Operation is the type of change to be made
63	Operation uint
64	// Modification is the attribute to be modified
65	Modification PartialAttribute
66}
67
68func (c *Change) encode() *ber.Packet {
69	change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
70	change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(c.Operation), "Operation"))
71	change.AppendChild(c.Modification.encode())
72	return change
73}
74
75// ModifyRequest as defined in https://tools.ietf.org/html/rfc4511
76type ModifyRequest struct {
77	// DN is the distinguishedName of the directory entry to modify
78	DN string
79	// Changes contain the attributes to modify
80	Changes []Change
81	// Controls hold optional controls to send with the request
82	Controls []Control
83}
84
85// Add appends the given attribute to the list of changes to be made
86func (req *ModifyRequest) Add(attrType string, attrVals []string) {
87	req.appendChange(AddAttribute, attrType, attrVals)
88}
89
90// Delete appends the given attribute to the list of changes to be made
91func (req *ModifyRequest) Delete(attrType string, attrVals []string) {
92	req.appendChange(DeleteAttribute, attrType, attrVals)
93}
94
95// Replace appends the given attribute to the list of changes to be made
96func (req *ModifyRequest) Replace(attrType string, attrVals []string) {
97	req.appendChange(ReplaceAttribute, attrType, attrVals)
98}
99
100func (req *ModifyRequest) appendChange(operation uint, attrType string, attrVals []string) {
101	req.Changes = append(req.Changes, Change{operation, PartialAttribute{Type: attrType, Vals: attrVals}})
102}
103
104func (req *ModifyRequest) appendTo(envelope *ber.Packet) error {
105	pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
106	pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.DN, "DN"))
107	changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
108	for _, change := range req.Changes {
109		changes.AppendChild(change.encode())
110	}
111	pkt.AppendChild(changes)
112
113	envelope.AppendChild(pkt)
114	if len(req.Controls) > 0 {
115		envelope.AppendChild(encodeControls(req.Controls))
116	}
117
118	return nil
119}
120
121// NewModifyRequest creates a modify request for the given DN
122func NewModifyRequest(dn string, controls []Control) *ModifyRequest {
123	return &ModifyRequest{
124		DN:       dn,
125		Controls: controls,
126	}
127}
128
129// Modify performs the ModifyRequest
130func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
131	msgCtx, err := l.doRequest(modifyRequest)
132	if err != nil {
133		return err
134	}
135	defer l.finishMessage(msgCtx)
136
137	packet, err := l.readPacket(msgCtx)
138	if err != nil {
139		return err
140	}
141
142	if packet.Children[1].Tag == ApplicationModifyResponse {
143		err := GetLDAPError(packet)
144		if err != nil {
145			return err
146		}
147	} else {
148		log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
149	}
150	return nil
151}
152