mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-15 18:08:45 -07:00
Also add some escaping for good measure.
This commit is contained in:
parent
9f8e6966d8
commit
3d0da5ac60
@ -260,7 +260,8 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|||||||
|
|
||||||
defer connection.Close()
|
defer connection.Close()
|
||||||
|
|
||||||
err = connection.Bind(ldapTemplateBindDN(cfg.BindDN, username), password)
|
bindDN := formatOptionalPercentS(cfg.BindDN, escapeForLDAPDN(username))
|
||||||
|
err = connection.Bind(bindDN, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warnln("LDAP Bind:", err)
|
l.Warnln("LDAP Bind:", err)
|
||||||
return false
|
return false
|
||||||
@ -280,7 +281,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|||||||
// the user. If this matches precisely one user then we are good to go.
|
// the user. If this matches precisely one user then we are good to go.
|
||||||
// The search filter uses the same %s interpolation as the bind DN.
|
// The search filter uses the same %s interpolation as the bind DN.
|
||||||
|
|
||||||
searchString := fmt.Sprintf(cfg.SearchFilter, username)
|
searchString := formatOptionalPercentS(cfg.SearchFilter, escapeForLDAPFilter(username))
|
||||||
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
|
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
|
||||||
const timeLimit = 60 // Search for up to a minute...
|
const timeLimit = 60 // Search for up to a minute...
|
||||||
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
|
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
|
||||||
@ -298,13 +299,37 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func ldapTemplateBindDN(bindDN string, username string) string {
|
// escapeForLDAPFilter escapes a value that will be used in a filter clause
|
||||||
// Check if formatting directives are included in the ldapTemplateBindDN - if so add username.
|
func escapeForLDAPFilter(value string) string {
|
||||||
// (%%s is a literal %s - unlikely for LDAP, but easy to handle here).
|
// https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Special_Characters
|
||||||
if strings.Count(bindDN, "%s") != strings.Count(bindDN, "%%s") {
|
// Backslash must always be first in the list so we don't double escape them.
|
||||||
bindDN = fmt.Sprintf(bindDN, username)
|
return escapeRunes(value, []rune{'\\', '*', '(', ')', 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeForLDAPDN escapes a value that will be used in a bind DN
|
||||||
|
func escapeForLDAPDN(value string) string {
|
||||||
|
// https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
|
||||||
|
// Backslash must always be first in the list so we don't double escape them.
|
||||||
|
return escapeRunes(value, []rune{'\\', ',', '#', '+', '<', '>', ';', '"', '=', ' ', 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeRunes(value string, runes []rune) string {
|
||||||
|
for _, e := range runes {
|
||||||
|
value = strings.ReplaceAll(value, string(e), fmt.Sprintf("\\%X", e))
|
||||||
}
|
}
|
||||||
return bindDN
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatOptionalPercentS(template string, username string) string {
|
||||||
|
var replacements []any
|
||||||
|
nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
|
||||||
|
if nReps < 0 {
|
||||||
|
nReps = 0
|
||||||
|
}
|
||||||
|
for i := 0; i < nReps; i++ {
|
||||||
|
replacements = append(replacements, username)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(template, replacements...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
|
// Convert an ISO-8859-1 encoded byte string to UTF-8. Works by the
|
||||||
|
@ -46,22 +46,67 @@ func TestStaticAuthPasswordFail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthLDAPSendsCorrectBindDNWithTemplate(t *testing.T) {
|
func TestFormatOptionalPercentS(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
templatedDn := ldapTemplateBindDN("cn=%s,dc=some,dc=example,dc=com", "username")
|
cases := []struct {
|
||||||
expectedDn := "cn=username,dc=some,dc=example,dc=com"
|
template string
|
||||||
if expectedDn != templatedDn {
|
username string
|
||||||
t.Fatalf("ldapTemplateBindDN should be %s != %s", expectedDn, templatedDn)
|
expected string
|
||||||
|
}{
|
||||||
|
{"cn=%s,dc=some,dc=example,dc=com", "username", "cn=username,dc=some,dc=example,dc=com"},
|
||||||
|
{"cn=fixedusername,dc=some,dc=example,dc=com", "username", "cn=fixedusername,dc=some,dc=example,dc=com"},
|
||||||
|
{"cn=%%s,dc=%s,dc=example,dc=com", "username", "cn=%s,dc=username,dc=example,dc=com"},
|
||||||
|
{"cn=%%s,dc=%%s,dc=example,dc=com", "username", "cn=%s,dc=%s,dc=example,dc=com"},
|
||||||
|
{"cn=%s,dc=%s,dc=example,dc=com", "username", "cn=username,dc=username,dc=example,dc=com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
templatedDn := formatOptionalPercentS(c.template, c.username)
|
||||||
|
if c.expected != templatedDn {
|
||||||
|
t.Fatalf("result should be %s != %s", c.expected, templatedDn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthLDAPSendsCorrectBindDNWithNoTemplate(t *testing.T) {
|
func TestEscapeForLDAPFilter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
templatedDn := ldapTemplateBindDN("cn=fixedusername,dc=some,dc=example,dc=com", "username")
|
cases := []struct {
|
||||||
expectedDn := "cn=fixedusername,dc=some,dc=example,dc=com"
|
in string
|
||||||
if expectedDn != templatedDn {
|
out string
|
||||||
t.Fatalf("ldapTemplateBindDN should be %s != %s", expectedDn, templatedDn)
|
}{
|
||||||
|
{"username", `username`},
|
||||||
|
{"user(name", `user\28name`},
|
||||||
|
{"user)name", `user\29name`},
|
||||||
|
{"user\\name", `user\5Cname`},
|
||||||
|
{"user*name", `user\2Aname`},
|
||||||
|
{"*,CN=asdf", `\2A,CN=asdf`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
res := escapeForLDAPFilter(c.in)
|
||||||
|
if c.out != res {
|
||||||
|
t.Fatalf("result should be %s != %s", c.out, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeForLDAPDN(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{"username", `username`},
|
||||||
|
{"* ,CN=asdf", `*\20\2CCN\3Dasdf`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
res := escapeForLDAPDN(c.in)
|
||||||
|
if c.out != res {
|
||||||
|
t.Fatalf("result should be %s != %s", c.out, res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user