From c106e438fb5a1cc4c6ed6ed66b7f35190d4aa7ed Mon Sep 17 00:00:00 2001 From: Taran Date: Mon, 8 Jul 2024 09:21:53 -0900 Subject: [PATCH 1/5] Add decode --- ldap/decode.go | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 ldap/decode.go diff --git a/ldap/decode.go b/ldap/decode.go new file mode 100644 index 0000000..cd98a5b --- /dev/null +++ b/ldap/decode.go @@ -0,0 +1,109 @@ +// The MIT License (MIT) + +// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) +// Portions copyright (c) 2015-2016 go-ldap Authors + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package ldap + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" +) + +// Remove leading and trailing spaces from the attribute type and value +// and unescape any escaped characters in these fields +// +// decodeDN is pulled from the go-ldap library +// https://github.com/go-ldap/ldap/blob/dbdc485259442f987d83e604cd4f5859cfc1be58/dn.go +func DecodeDN(str string) (string, error) { + s := []rune(stripLeadingAndTrailingSpaces(str)) + + builder := strings.Builder{} + for i := 0; i < len(s); i++ { + char := s[i] + + // If the character is not an escape character, just add it to the + // builder and continue + if char != '\\' { + builder.WriteRune(char) + continue + } + + // If the escape character is the last character, it's a corrupted + // escaped character + if i+1 >= len(s) { + return "", fmt.Errorf("got corrupted escaped character: '%s'", string(s)) + } + + // If the escaped character is a special character, just add it to + // the builder and continue + switch s[i+1] { + case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': + builder.WriteRune(s[i+1]) + i++ + continue + } + + // If the escaped character is not a special character, it should + // be a hex-encoded character of the form \XX if it's not at least + // two characters long, it's a corrupted escaped character + if i+2 >= len(s) { + return "", errors.New("failed to decode escaped character: encoding/hex: invalid byte: " + string(s[i+1])) + } + + // Get the runes for the two characters after the escape character + // and convert them to a byte slice + xx := []byte(string(s[i+1 : i+3])) + + // If the two runes are not hex characters and result in more than + // two bytes when converted to a byte slice, it's a corrupted + // escaped character + if len(xx) != 2 { + return "", errors.New("failed to decode escaped character: invalid byte: " + string(xx)) + } + + // Decode the hex-encoded character and add it to the builder + dst := []byte{0} + if n, err := hex.Decode(dst, xx); err != nil { + return "", errors.New("failed to decode escaped character: " + err.Error()) + } else if n != 1 { + return "", fmt.Errorf("failed to decode escaped character: encoding/hex: expected 1 byte when un-escaping, got %d", n) + } + + builder.WriteByte(dst[0]) + i += 2 + } + + return builder.String(), nil +} + +func stripLeadingAndTrailingSpaces(inVal string) string { + noSpaces := strings.Trim(inVal, " ") + + // Re-add the trailing space if it was an escaped space + if len(noSpaces) > 0 && noSpaces[len(noSpaces)-1] == '\\' && inVal[len(inVal)-1] == ' ' { + noSpaces += " " + } + + return noSpaces +} From 7a40d7a2a0a00f568f3992ae822f9b9e958a7488 Mon Sep 17 00:00:00 2001 From: Taran Date: Mon, 8 Jul 2024 11:53:29 -0900 Subject: [PATCH 2/5] tests --- ldap/decode_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 ldap/decode_test.go diff --git a/ldap/decode_test.go b/ldap/decode_test.go new file mode 100644 index 0000000..288d638 --- /dev/null +++ b/ldap/decode_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2015-2022 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package ldap + +import ( + "errors" + "fmt" + "testing" +) + +func TestDecodeDN(t *testing.T) { + testCases := []struct { + input string + expected string + err error + }{ + { + input: "cn=foo,dc=example,dc=com", + expected: "cn=foo,dc=example,dc=com", + }, + { + input: `cn=\d0\98\d0\b2\d0\b0\d0\bd\d0\be\d0\b2 \d0\98\d0\b2\d0\b0\d0\bd,dc=example,dc=com`, + expected: "cn=Иванов Иван,dc=example,dc=com", + }, + { + input: `cn=\20foo,dc=example,dc=com`, + expected: "cn= foo,dc=example,dc=com", + }, + { + input: `cn=pr\c3\bcfen,dc=example,dc=com`, + expected: "cn=prüfen,dc=example,dc=com", + }, + { + input: `cn=foo,dc=example,dc=com\`, + err: fmt.Errorf("got corrupted escaped character: '%s'", `cn=foo,dc=example,dc=com\`), + }, + { + input: `cn=foo,dc=example,dc=com\a`, + err: fmt.Errorf("ailed to decode escaped character: encoding/hex: invalid byte: %s", "a"), + }, + } + for i, testCase := range testCases { + t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) { + output, err := DecodeDN(testCase.input) + if err != nil && testCase.err == nil { + t.Fatalf("unexpected error: %v", err) + } + if testCase.err != nil && errors.Is(err, testCase.err) { + t.Fatalf("expected error `%v`, got `%v`", testCase.err, err) + } + if output != testCase.expected { + t.Fatalf("expected %q, got %q", testCase.expected, output) + } + }) + } +} From 9d13539f58e6cfa29c143335fe0558784921433b Mon Sep 17 00:00:00 2001 From: Taran Date: Mon, 8 Jul 2024 12:26:08 -0900 Subject: [PATCH 3/5] lint --- ldap/decode.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ldap/decode.go b/ldap/decode.go index cd98a5b..35a27fd 100644 --- a/ldap/decode.go +++ b/ldap/decode.go @@ -30,10 +30,10 @@ import ( "strings" ) -// Remove leading and trailing spaces from the attribute type and value +// DecodeDN - remove leading and trailing spaces from the attribute type and value // and unescape any escaped characters in these fields // -// decodeDN is pulled from the go-ldap library +// pulled from the go-ldap library // https://github.com/go-ldap/ldap/blob/dbdc485259442f987d83e604cd4f5859cfc1be58/dn.go func DecodeDN(str string) (string, error) { s := []rune(stripLeadingAndTrailingSpaces(str)) From 800599671f6a43da83f62feda7a6f64d5083d6d3 Mon Sep 17 00:00:00 2001 From: Taran Date: Mon, 8 Jul 2024 14:04:16 -0900 Subject: [PATCH 4/5] changes to decode, add tests --- ldap/{decode.go => decode_dn_contrib.go} | 12 ++++---- ldap/decode_test.go | 36 ++++++++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) rename ldap/{decode.go => decode_dn_contrib.go} (84%) diff --git a/ldap/decode.go b/ldap/decode_dn_contrib.go similarity index 84% rename from ldap/decode.go rename to ldap/decode_dn_contrib.go index 35a27fd..03933b1 100644 --- a/ldap/decode.go +++ b/ldap/decode_dn_contrib.go @@ -28,6 +28,8 @@ import ( "errors" "fmt" "strings" + + ldap "github.com/go-ldap/ldap/v3" ) // DecodeDN - remove leading and trailing spaces from the attribute type and value @@ -52,7 +54,7 @@ func DecodeDN(str string) (string, error) { // If the escape character is the last character, it's a corrupted // escaped character if i+1 >= len(s) { - return "", fmt.Errorf("got corrupted escaped character: '%s'", string(s)) + return "", ldap.NewError(34, fmt.Errorf("got corrupted escaped character: '%s'", string(s))) } // If the escaped character is a special character, just add it to @@ -68,7 +70,7 @@ func DecodeDN(str string) (string, error) { // be a hex-encoded character of the form \XX if it's not at least // two characters long, it's a corrupted escaped character if i+2 >= len(s) { - return "", errors.New("failed to decode escaped character: encoding/hex: invalid byte: " + string(s[i+1])) + return "", ldap.NewError(34, errors.New("unable to decode escaped character: encoding/hex: invalid byte: "+string(s[i+1]))) } // Get the runes for the two characters after the escape character @@ -79,15 +81,15 @@ func DecodeDN(str string) (string, error) { // two bytes when converted to a byte slice, it's a corrupted // escaped character if len(xx) != 2 { - return "", errors.New("failed to decode escaped character: invalid byte: " + string(xx)) + return "", ldap.NewError(34, fmt.Errorf("unable to decode escaped character: invalid byte: %s", string(xx))) } // Decode the hex-encoded character and add it to the builder dst := []byte{0} if n, err := hex.Decode(dst, xx); err != nil { - return "", errors.New("failed to decode escaped character: " + err.Error()) + return "", ldap.NewError(34, errors.New("unable to decode escaped character: "+err.Error())) } else if n != 1 { - return "", fmt.Errorf("failed to decode escaped character: encoding/hex: expected 1 byte when un-escaping, got %d", n) + return "", ldap.NewError(34, fmt.Errorf("unable to decode escaped character: encoding/hex: expected 1 byte when un-escaping, got %d", n)) } builder.WriteByte(dst[0]) diff --git a/ldap/decode_test.go b/ldap/decode_test.go index 288d638..cc10a2a 100644 --- a/ldap/decode_test.go +++ b/ldap/decode_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2022 MinIO, Inc. +// Copyright (c) 2015-2024 MinIO, Inc. // // This file is part of MinIO Object Storage stack // @@ -34,24 +34,44 @@ func TestDecodeDN(t *testing.T) { expected: "cn=foo,dc=example,dc=com", }, { - input: `cn=\d0\98\d0\b2\d0\b0\d0\bd\d0\be\d0\b2 \d0\98\d0\b2\d0\b0\d0\bd,dc=example,dc=com`, - expected: "cn=Иванов Иван,dc=example,dc=com", - }, - { - input: `cn=\20foo,dc=example,dc=com`, - expected: "cn= foo,dc=example,dc=com", + input: `cn=\d0\bf\d1\80\d0\b5\d1\86\d0\b5\d0\b4\d0\b5\d0\bd\d1\82 \d1\82\d0\b5\d1\81\d1\82,dc=example,dc=com`, + expected: "cn=прецедент тест,dc=example,dc=com", }, { input: `cn=pr\c3\bcfen,dc=example,dc=com`, expected: "cn=prüfen,dc=example,dc=com", }, + { + input: `cn=fo\20o,dc=example,dc=com`, + expected: "cn=fo o,dc=example,dc=com", + }, + { + input: `cn=\e6\b5\8b\e8\af\95,dc=example,dc=com`, + expected: "cn=测试,dc=example,dc=com", + }, + { + input: `cn=\e6\b8\ac\e8\a9\a6,dc=example,dc=com`, + expected: "cn=測試,dc=example,dc=com", + }, + { + input: `cn=svc\ef\b9\92algorithm,dc=example,dc=com`, + expected: "cn=svc﹒algorithm,dc=example,dc=com", + }, + { + input: `cn=\e0\a4\9c\e0\a4\be\e0\a4\81\e0\a4\9a,dc=example,dc=com`, + expected: "cn=जाँच,dc=example,dc=com", + }, + { + input: `cn=\f0\9f\a7\aa\f0\9f\93\9d,dc=example,dc=com`, + expected: "cn=🧪📝,dc=example,dc=com", + }, { input: `cn=foo,dc=example,dc=com\`, err: fmt.Errorf("got corrupted escaped character: '%s'", `cn=foo,dc=example,dc=com\`), }, { input: `cn=foo,dc=example,dc=com\a`, - err: fmt.Errorf("ailed to decode escaped character: encoding/hex: invalid byte: %s", "a"), + err: fmt.Errorf("unable to decode escaped character: encoding/hex: invalid byte: %s", "a"), }, } for i, testCase := range testCases { From 1fb25d482e0318c4c87e9b8c7bb9200b2f680efc Mon Sep 17 00:00:00 2001 From: Taran Date: Mon, 8 Jul 2024 14:24:33 -0900 Subject: [PATCH 5/5] rename test --- ldap/{decode_test.go => decode_dn_contrib_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ldap/{decode_test.go => decode_dn_contrib_test.go} (100%) diff --git a/ldap/decode_test.go b/ldap/decode_dn_contrib_test.go similarity index 100% rename from ldap/decode_test.go rename to ldap/decode_dn_contrib_test.go