Skip to content

Commit b64a808

Browse files
authored
implement server side sorting controls (rfc2891) (#414)
* implement server side sorting controls (rfc2891) * implement NewControlServerSideSorting to decode server side sorting request, add server side sorting type to ControlMap
1 parent a79fb6b commit b64a808

File tree

2 files changed

+257
-7
lines changed

2 files changed

+257
-7
lines changed

v3/control.go

+204-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ const (
2323
// ControlTypeSubtreeDelete - https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02
2424
ControlTypeSubtreeDelete = "1.2.840.113556.1.4.805"
2525

26+
// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
27+
ControlTypeServerSideSorting = "1.2.840.113556.1.4.473"
28+
// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
29+
ControlTypeServerSideSortingResult = "1.2.840.113556.1.4.474"
30+
2631
// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
2732
ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528"
2833
// ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
@@ -33,13 +38,15 @@ const (
3338

3439
// ControlTypeMap maps controls to text descriptions
3540
var ControlTypeMap = map[string]string{
36-
ControlTypePaging: "Paging",
37-
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
38-
ControlTypeManageDsaIT: "Manage DSA IT",
39-
ControlTypeSubtreeDelete: "Subtree Delete Control",
40-
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
41-
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
42-
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
41+
ControlTypePaging: "Paging",
42+
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
43+
ControlTypeManageDsaIT: "Manage DSA IT",
44+
ControlTypeSubtreeDelete: "Subtree Delete Control",
45+
ControlTypeMicrosoftNotification: "Change Notification - Microsoft",
46+
ControlTypeMicrosoftShowDeleted: "Show Deleted Objects - Microsoft",
47+
ControlTypeMicrosoftServerLinkTTL: "Return TTL-DNs for link values with associated expiry times - Microsoft",
48+
ControlTypeServerSideSorting: "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
49+
ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
4350
}
4451

4552
// Control defines an interface controls provide to encode and describe themselves
@@ -490,6 +497,10 @@ func DecodeControl(packet *ber.Packet) (Control, error) {
490497
return NewControlMicrosoftServerLinkTTL(), nil
491498
case ControlTypeSubtreeDelete:
492499
return NewControlSubtreeDelete(), nil
500+
case ControlTypeServerSideSorting:
501+
return NewControlServerSideSorting(value)
502+
case ControlTypeServerSideSortingResult:
503+
return NewControlServerSideSortingResult(value)
493504
default:
494505
c := new(ControlString)
495506
c.ControlType = ControlType
@@ -560,3 +571,189 @@ func encodeControls(controls []Control) *ber.Packet {
560571
}
561572
return packet
562573
}
574+
575+
// ControlServerSideSorting
576+
577+
type SortKey struct {
578+
Reverse bool
579+
AttributeType string
580+
MatchingRule string
581+
}
582+
583+
type ControlServerSideSorting struct {
584+
SortKeys []*SortKey
585+
}
586+
587+
func (c *ControlServerSideSorting) GetControlType() string {
588+
return ControlTypeServerSideSorting
589+
}
590+
591+
func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) {
592+
sortKeys := []*SortKey{}
593+
594+
val := value.Children[1].Children
595+
596+
if len(val) != 1 {
597+
return nil, fmt.Errorf("no sequence value in packet")
598+
}
599+
600+
sequences := val[0].Children
601+
602+
for i, sequence := range sequences {
603+
sortKey := &SortKey{}
604+
605+
if len(sequence.Children) < 2 {
606+
return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i)
607+
}
608+
609+
sortKey.AttributeType = sequence.Children[0].Value.(string)
610+
sortKey.MatchingRule = sequence.Children[1].Value.(string)
611+
612+
if len(sequence.Children) == 3 {
613+
sortKey.Reverse = sequence.Children[2].Value.(bool)
614+
}
615+
616+
sortKeys = append(sortKeys, sortKey)
617+
}
618+
619+
return &ControlServerSideSorting{SortKeys: sortKeys}, nil
620+
}
621+
622+
func NewControlServerSideSortingWithSortKeys(sortKeys []*SortKey) *ControlServerSideSorting {
623+
return &ControlServerSideSorting{SortKeys: sortKeys}
624+
}
625+
626+
func (c *ControlServerSideSorting) Encode() *ber.Packet {
627+
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
628+
control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type")
629+
630+
value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value")
631+
seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList")
632+
633+
for _, f := range c.SortKeys {
634+
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "")
635+
636+
seq.AppendChild(
637+
ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"),
638+
)
639+
seq.AppendChild(
640+
ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"),
641+
)
642+
if f.Reverse {
643+
seq.AppendChild(
644+
ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"),
645+
)
646+
}
647+
648+
seqs.AppendChild(seq)
649+
}
650+
651+
value.AppendChild(seqs)
652+
653+
packet.AppendChild(control)
654+
packet.AppendChild(value)
655+
656+
return packet
657+
}
658+
659+
func (c *ControlServerSideSorting) String() string {
660+
return fmt.Sprintf(
661+
"Control Type: %s (%q) Criticality:%t %+v",
662+
"Server Side Sorting",
663+
c.GetControlType(),
664+
false,
665+
c.SortKeys,
666+
)
667+
}
668+
669+
// ControlServerSideSortingResponse
670+
671+
const (
672+
ControlServerSideSortingCodeSuccess ControlServerSideSortingCode = 0
673+
ControlServerSideSortingCodeOperationsError ControlServerSideSortingCode = 1
674+
ControlServerSideSortingCodeTimeLimitExceeded ControlServerSideSortingCode = 2
675+
ControlServerSideSortingCodeStrongAuthRequired ControlServerSideSortingCode = 8
676+
ControlServerSideSortingCodeAdminLimitExceeded ControlServerSideSortingCode = 11
677+
ControlServerSideSortingCodeNoSuchAttribute ControlServerSideSortingCode = 16
678+
ControlServerSideSortingCodeInappropriateMatching ControlServerSideSortingCode = 18
679+
ControlServerSideSortingCodeInsufficientAccessRights ControlServerSideSortingCode = 50
680+
ControlServerSideSortingCodeBusy ControlServerSideSortingCode = 51
681+
ControlServerSideSortingCodeUnwillingToPerform ControlServerSideSortingCode = 53
682+
ControlServerSideSortingCodeOther ControlServerSideSortingCode = 80
683+
)
684+
685+
var ControlServerSideSortingCodes = []ControlServerSideSortingCode{
686+
ControlServerSideSortingCodeSuccess,
687+
ControlServerSideSortingCodeOperationsError,
688+
ControlServerSideSortingCodeTimeLimitExceeded,
689+
ControlServerSideSortingCodeStrongAuthRequired,
690+
ControlServerSideSortingCodeAdminLimitExceeded,
691+
ControlServerSideSortingCodeNoSuchAttribute,
692+
ControlServerSideSortingCodeInappropriateMatching,
693+
ControlServerSideSortingCodeInsufficientAccessRights,
694+
ControlServerSideSortingCodeBusy,
695+
ControlServerSideSortingCodeUnwillingToPerform,
696+
ControlServerSideSortingCodeOther,
697+
}
698+
699+
type ControlServerSideSortingCode int64
700+
701+
// Valid test the code contained in the control against the ControlServerSideSortingCodes slice and return an error if the code is unknown.
702+
func (c ControlServerSideSortingCode) Valid() error {
703+
for _, validRet := range ControlServerSideSortingCodes {
704+
if c == validRet {
705+
return nil
706+
}
707+
}
708+
return fmt.Errorf("unknown return code : %d", c)
709+
}
710+
711+
func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) {
712+
control := &ControlServerSideSortingResult{}
713+
714+
if pkt == nil || len(pkt.Children) == 0 {
715+
return nil, fmt.Errorf("bad packet")
716+
}
717+
718+
codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes())
719+
if err != nil {
720+
return nil, err
721+
}
722+
723+
code := ControlServerSideSortingCode(codeInt)
724+
if err := code.Valid(); err != nil {
725+
return nil, err
726+
}
727+
728+
return control, nil
729+
}
730+
731+
type ControlServerSideSortingResult struct {
732+
Criticality bool
733+
734+
Result ControlServerSideSortingCode
735+
736+
// Not populated for now. I can't get openldap to send me this value, so I think this is specific to other directory server
737+
// AttributeType string
738+
}
739+
740+
func (control *ControlServerSideSortingResult) GetControlType() string {
741+
return ControlTypeServerSideSortingResult
742+
}
743+
func (c *ControlServerSideSortingResult) Encode() *ber.Packet {
744+
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortResult sequence")
745+
sortResult := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(c.Result), "SortResult")
746+
packet.AppendChild(sortResult)
747+
748+
return packet
749+
}
750+
751+
func (c *ControlServerSideSortingResult) String() string {
752+
return fmt.Sprintf(
753+
"Control Type: %s (%q) Criticality:%t ResultCode:%+v",
754+
"Server Side Sorting Result",
755+
c.GetControlType(),
756+
c.Criticality,
757+
c.Result,
758+
)
759+
}

v3/control_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,56 @@ func TestDecodeControl(t *testing.T) {
210210
})
211211
}
212212
}
213+
214+
func TestControlServerSideSortingDecoding(t *testing.T) {
215+
control := NewControlServerSideSortingWithSortKeys([]*SortKey{{
216+
MatchingRule: "foo",
217+
AttributeType: "foobar",
218+
Reverse: true,
219+
}, {
220+
MatchingRule: "foo",
221+
AttributeType: "foobar",
222+
Reverse: false,
223+
}, {
224+
MatchingRule: "",
225+
AttributeType: "",
226+
Reverse: false,
227+
}, {
228+
MatchingRule: "totoRule",
229+
AttributeType: "",
230+
Reverse: false,
231+
}, {
232+
MatchingRule: "",
233+
AttributeType: "totoType",
234+
Reverse: false,
235+
}})
236+
237+
controlDecoded, err := NewControlServerSideSorting(control.Encode())
238+
if err != nil {
239+
t.Fatal(err)
240+
}
241+
242+
if control.GetControlType() != controlDecoded.GetControlType() {
243+
t.Fatalf("control type mismatch: control:%s - decoded:%s", control.GetControlType(), controlDecoded.GetControlType())
244+
}
245+
246+
if len(control.SortKeys) != len(controlDecoded.SortKeys) {
247+
t.Fatalf("sort keys length mismatch (control: %d - decoded: %d)", len(control.SortKeys), len(controlDecoded.SortKeys))
248+
}
249+
250+
for i, sk := range control.SortKeys {
251+
dsk := controlDecoded.SortKeys[i]
252+
253+
if sk.AttributeType != dsk.AttributeType {
254+
t.Fatalf("attribute type mismatch for sortkey %d", i)
255+
}
256+
257+
if sk.MatchingRule != dsk.MatchingRule {
258+
t.Fatalf("matching rule mismatch for sortkey %d", i)
259+
}
260+
261+
if sk.Reverse != dsk.Reverse {
262+
t.Fatalf("reverse mismtach for sortkey %d", i)
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)