mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
Add ldap filter syntax validation when create search filter
Correct ldap search filter is enclosed with '(' and ')' Search ldap group with the ldap group base DN instead of group DN Fixes #12613 LDAP Group Filter and Group Base DN have no affect Signed-off-by: stonezdj <stonezdj@gmail.com>
This commit is contained in:
parent
bc3433194b
commit
b9752f3112
75
src/common/utils/ldap/filter.go
Normal file
75
src/common/utils/ldap/filter.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
ber "gopkg.in/asn1-ber.v1"
|
||||||
|
goldap "gopkg.in/ldap.v2"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterBuilder build filter for ldap search
|
||||||
|
type FilterBuilder struct {
|
||||||
|
packet *ber.Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or ...
|
||||||
|
func (f *FilterBuilder) Or(filterB *FilterBuilder) *FilterBuilder {
|
||||||
|
if f.packet == nil {
|
||||||
|
return filterB
|
||||||
|
}
|
||||||
|
if filterB.packet == nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
p := ber.Encode(ber.ClassContext, ber.TypeConstructed, goldap.FilterOr, nil, goldap.FilterMap[goldap.FilterOr])
|
||||||
|
p.AppendChild(f.packet)
|
||||||
|
p.AppendChild(filterB.packet)
|
||||||
|
return &FilterBuilder{packet: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And ...
|
||||||
|
func (f *FilterBuilder) And(filterB *FilterBuilder) *FilterBuilder {
|
||||||
|
if f.packet == nil {
|
||||||
|
return filterB
|
||||||
|
}
|
||||||
|
if filterB.packet == nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
p := ber.Encode(ber.ClassContext, ber.TypeConstructed, goldap.FilterAnd, nil, goldap.FilterMap[goldap.FilterAnd])
|
||||||
|
p.AppendChild(f.packet)
|
||||||
|
p.AppendChild(filterB.packet)
|
||||||
|
return &FilterBuilder{packet: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String ...
|
||||||
|
func (f *FilterBuilder) String() (string, error) {
|
||||||
|
if f.packet == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return goldap.DecompileFilter(f.packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilterBuilder parse FilterBuilder from string
|
||||||
|
func NewFilterBuilder(filter string) (*FilterBuilder, error) {
|
||||||
|
f := normalizeFilter(filter)
|
||||||
|
if len(strings.TrimSpace(f)) == 0 {
|
||||||
|
return &FilterBuilder{}, nil
|
||||||
|
}
|
||||||
|
p, err := goldap.CompileFilter(f)
|
||||||
|
if err != nil {
|
||||||
|
return &FilterBuilder{}, ErrInvalidFilter
|
||||||
|
}
|
||||||
|
return &FilterBuilder{packet: p}, nil
|
||||||
|
}
|
109
src/common/utils/ldap/filter_test.go
Normal file
109
src/common/utils/ldap/filter_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright Project Harbor Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterBuilder(t *testing.T) {
|
||||||
|
fb, err := NewFilterBuilder("(objectclass=groupOfNames)")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
fb2, err := NewFilterBuilder("(cn=admin)")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fb3 := fb.And(fb2)
|
||||||
|
result, err := fb3.String()
|
||||||
|
assert.Equal(t, result, "(&(objectclass=groupOfNames)(cn=admin))")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterBuilder_And(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
filterA string
|
||||||
|
filterB string
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in args
|
||||||
|
want string
|
||||||
|
wantError error
|
||||||
|
}{
|
||||||
|
{name: `normal`, in: args{"(objectclass=groupOfNames)", "(cn=admin)"}, want: "(&(objectclass=groupOfNames)(cn=admin))", wantError: nil},
|
||||||
|
{name: `empty left`, in: args{"", "(cn=admin)"}, want: "(cn=admin))", wantError: nil},
|
||||||
|
{name: `empty right`, in: args{"(cn=admin)", ""}, want: "(cn=admin))", wantError: nil},
|
||||||
|
{name: `both empty`, in: args{"", ""}, want: "", wantError: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
fbA, err := NewFilterBuilder(tt.in.filterA)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
fbB, err := NewFilterBuilder(tt.in.filterB)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fbB)
|
||||||
|
}
|
||||||
|
got, err := fbA.And(fbB).String()
|
||||||
|
if got != tt.want && err != tt.wantError {
|
||||||
|
t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterBuilder_Or(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
filterA string
|
||||||
|
filterB string
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in args
|
||||||
|
want string
|
||||||
|
wantError error
|
||||||
|
}{
|
||||||
|
{name: `normal`, in: args{"(objectclass=groupOfNames)", "(cn=admin)"}, want: "(|(objectclass=groupOfNames)(cn=admin))", wantError: nil},
|
||||||
|
{name: `empty left`, in: args{"", "(cn=admin)"}, want: "(cn=admin))", wantError: nil},
|
||||||
|
{name: `empty right`, in: args{"(cn=admin)", ""}, want: "(cn=admin))", wantError: nil},
|
||||||
|
{name: `both empty`, in: args{"", ""}, want: "", wantError: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
fbA, err := NewFilterBuilder(tt.in.filterA)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
fbB, err := NewFilterBuilder(tt.in.filterB)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fbB)
|
||||||
|
}
|
||||||
|
got, err := fbA.Or(fbB).String()
|
||||||
|
if got != tt.want && err != tt.wantError {
|
||||||
|
t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,10 @@ import (
|
|||||||
var ErrNotFound = errors.New("entity not found")
|
var ErrNotFound = errors.New("entity not found")
|
||||||
|
|
||||||
// ErrDNSyntax ...
|
// ErrDNSyntax ...
|
||||||
var ErrDNSyntax = errors.New("Invalid DN syntax")
|
var ErrDNSyntax = errors.New("invalid DN syntax")
|
||||||
|
|
||||||
|
// ErrInvalidFilter ...
|
||||||
|
var ErrInvalidFilter = errors.New("invalid filter syntax")
|
||||||
|
|
||||||
// Session - define a LDAP session
|
// Session - define a LDAP session
|
||||||
type Session struct {
|
type Session struct {
|
||||||
@ -186,9 +189,12 @@ func ConnectionTestWithAllConfig(ldapConfig models.LdapConf, ldapGroupConfig mod
|
|||||||
// SearchUser - search LDAP user by name
|
// SearchUser - search LDAP user by name
|
||||||
func (session *Session) SearchUser(username string) ([]models.LdapUser, error) {
|
func (session *Session) SearchUser(username string) ([]models.LdapUser, error) {
|
||||||
var ldapUsers []models.LdapUser
|
var ldapUsers []models.LdapUser
|
||||||
ldapFilter := session.createUserFilter(username)
|
ldapFilter, err := createUserSearchFilter(session.ldapConfig.LdapFilter, session.ldapConfig.LdapUID, username)
|
||||||
result, err := session.SearchLdap(ldapFilter)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := session.SearchLdap(ldapFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -292,6 +298,10 @@ func (session *Session) SearchLdapAttribute(baseDN, filter string, attributes []
|
|||||||
if !(strings.HasPrefix(filter, "(") || strings.HasSuffix(filter, ")")) {
|
if !(strings.HasPrefix(filter, "(") || strings.HasSuffix(filter, ")")) {
|
||||||
filter = "(" + filter + ")"
|
filter = "(" + filter + ")"
|
||||||
}
|
}
|
||||||
|
if _, err := goldap.CompileFilter(filter); err != nil {
|
||||||
|
log.Errorf("Wrong filter format, filter:%v", filter)
|
||||||
|
return nil, ErrInvalidFilter
|
||||||
|
}
|
||||||
log.Debugf("Search ldap with filter:%v", filter)
|
log.Debugf("Search ldap with filter:%v", filter)
|
||||||
searchRequest := goldap.NewSearchRequest(
|
searchRequest := goldap.NewSearchRequest(
|
||||||
baseDN,
|
baseDN,
|
||||||
@ -321,28 +331,24 @@ func (session *Session) SearchLdapAttribute(baseDN, filter string, attributes []
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUserFilter - create filter to search user with specified username
|
// createUserSearchFilter - create filter to search user with specified username
|
||||||
func (session *Session) createUserFilter(username string) string {
|
func createUserSearchFilter(origFilter, ldapUID, username string) (string, error) {
|
||||||
|
oFilter, err := NewFilterBuilder(origFilter)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
var filterTag string
|
var filterTag string
|
||||||
|
filterTag = goldap.EscapeFilter(username)
|
||||||
if username == "" {
|
if len(filterTag) == 0 {
|
||||||
filterTag = "*"
|
filterTag = "*"
|
||||||
} else {
|
|
||||||
filterTag = goldap.EscapeFilter(username)
|
|
||||||
}
|
}
|
||||||
|
uFilterStr := fmt.Sprintf("(%v=%v)", ldapUID, filterTag)
|
||||||
ldapFilter := normalizeFilter(session.ldapConfig.LdapFilter)
|
uFilter, err := NewFilterBuilder(uFilterStr)
|
||||||
ldapUID := session.ldapConfig.LdapUID
|
if err != nil {
|
||||||
|
return "", err
|
||||||
if ldapFilter == "" {
|
|
||||||
ldapFilter = "(" + ldapUID + "=" + filterTag + ")"
|
|
||||||
} else {
|
|
||||||
ldapFilter = "(&(" + ldapFilter + ")(" + ldapUID + "=" + filterTag + "))"
|
|
||||||
}
|
}
|
||||||
|
filter := oFilter.And(uFilter)
|
||||||
log.Debug("ldap filter :", ldapFilter)
|
return filter.String()
|
||||||
|
|
||||||
return ldapFilter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close - close current session
|
// Close - close current session
|
||||||
@ -375,17 +381,31 @@ func (session *Session) SearchGroupByDN(groupDN string) ([]models.LdapGroup, err
|
|||||||
return groupList, err
|
return groupList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) searchGroup(baseDN, filter, groupName, groupNameAttribute string) ([]models.LdapGroup, error) {
|
func (session *Session) groupBaseDN() string {
|
||||||
|
if len(session.ldapGroupConfig.LdapGroupBaseDN) == 0 {
|
||||||
|
return session.ldapConfig.LdapBaseDn
|
||||||
|
}
|
||||||
|
return session.ldapGroupConfig.LdapGroupBaseDN
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *Session) searchGroup(groupDN, filter, groupName, groupNameAttribute string) ([]models.LdapGroup, error) {
|
||||||
ldapGroups := make([]models.LdapGroup, 0)
|
ldapGroups := make([]models.LdapGroup, 0)
|
||||||
log.Debugf("Groupname: %v, basedn: %v", groupName, baseDN)
|
log.Debugf("Groupname: %v, groupDN: %v", groupName, groupDN)
|
||||||
ldapFilter := createGroupSearchFilter(filter, groupName, groupNameAttribute)
|
ldapFilter, err := createGroupSearchFilter(filter, groupName, groupNameAttribute)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("wrong filter format: filter:%v, groupName:%v, groupNameAttribute:%v", filter, groupName, groupNameAttribute)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
attributes := []string{groupNameAttribute}
|
attributes := []string{groupNameAttribute}
|
||||||
result, err := session.SearchLdapAttribute(baseDN, ldapFilter, attributes)
|
result, err := session.SearchLdapAttribute(session.groupBaseDN(), ldapFilter, attributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, ldapEntry := range result.Entries {
|
for _, ldapEntry := range result.Entries {
|
||||||
var group models.LdapGroup
|
var group models.LdapGroup
|
||||||
|
if groupDN != ldapEntry.DN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
group.GroupDN = ldapEntry.DN
|
group.GroupDN = ldapEntry.DN
|
||||||
for _, attr := range ldapEntry.Attributes {
|
for _, attr := range ldapEntry.Attributes {
|
||||||
// OpenLdap sometimes contain leading space in username
|
// OpenLdap sometimes contain leading space in username
|
||||||
@ -401,40 +421,34 @@ func (session *Session) searchGroup(baseDN, filter, groupName, groupNameAttribut
|
|||||||
return ldapGroups, nil
|
return ldapGroups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGroupSearchFilter(oldFilter, groupName, groupNameAttribute string) string {
|
func createGroupSearchFilter(oldFilterStr, groupName, groupNameAttribute string) (string, error) {
|
||||||
filter := ""
|
origFilter, err := NewFilterBuilder(oldFilterStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to create group search filter:%v", oldFilterStr)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
groupName = goldap.EscapeFilter(groupName)
|
groupName = goldap.EscapeFilter(groupName)
|
||||||
groupNameAttribute = goldap.EscapeFilter(groupNameAttribute)
|
gFilterStr := ""
|
||||||
oldFilter = normalizeFilter(oldFilter)
|
if len(groupName) > 0 {
|
||||||
if len(oldFilter) == 0 {
|
gFilterStr = fmt.Sprintf("(%v=%v)", goldap.EscapeFilter(groupNameAttribute), groupName)
|
||||||
if len(groupName) == 0 {
|
|
||||||
filter = groupNameAttribute + "=*"
|
|
||||||
} else {
|
|
||||||
filter = groupNameAttribute + "=*" + groupName + "*"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(groupName) == 0 {
|
|
||||||
filter = oldFilter
|
|
||||||
} else {
|
|
||||||
filter = "(&(" + oldFilter + ")(" + groupNameAttribute + "=*" + groupName + "*))"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return filter
|
gFilter, err := NewFilterBuilder(gFilterStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("invalid ldap filter:%v", gFilterStr)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fb := origFilter.And(gFilter)
|
||||||
|
return fb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(s []string, e string) bool {
|
// normalizeFilter - add '(' and ')' in ldap filter if it doesn't exist
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeFilter - remove '(' and ')' in ldap filter
|
|
||||||
func normalizeFilter(filter string) string {
|
func normalizeFilter(filter string) string {
|
||||||
norFilter := strings.TrimSpace(filter)
|
norFilter := strings.TrimSpace(filter)
|
||||||
norFilter = strings.TrimPrefix(norFilter, "(")
|
if len(norFilter) == 0 {
|
||||||
norFilter = strings.TrimSuffix(norFilter, ")")
|
return norFilter
|
||||||
return norFilter
|
}
|
||||||
|
if strings.HasPrefix(norFilter, "(") && strings.HasSuffix(norFilter, ")") {
|
||||||
|
return norFilter
|
||||||
|
}
|
||||||
|
return "(" + norFilter + ")"
|
||||||
}
|
}
|
||||||
|
@ -234,18 +234,21 @@ func Test_createGroupSearchFilter(t *testing.T) {
|
|||||||
groupNameAttribute string
|
groupNameAttribute string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want string
|
want string
|
||||||
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{"Normal Filter", args{oldFilter: "objectclass=groupOfNames", groupName: "harbor_users", groupNameAttribute: "cn"}, "(&(objectclass=groupOfNames)(cn=*harbor_users*))"},
|
{"Normal Filter", args{oldFilter: "objectclass=groupOfNames", groupName: "harbor_users", groupNameAttribute: "cn"}, "(&(objectclass=groupOfNames)(cn=*harbor_users*))", nil},
|
||||||
{"Empty Old", args{groupName: "harbor_users", groupNameAttribute: "cn"}, "cn=*harbor_users*"},
|
{"Empty Old", args{groupName: "harbor_users", groupNameAttribute: "cn"}, "(cn=*harbor_users*)", nil},
|
||||||
{"Empty Both", args{groupNameAttribute: "cn"}, "cn=*"},
|
{"Empty Both", args{groupNameAttribute: "cn"}, "(cn=*)", nil},
|
||||||
{"Empty name", args{oldFilter: "objectclass=groupOfNames", groupNameAttribute: "cn"}, "objectclass=groupOfNames"},
|
{"Empty name", args{oldFilter: "objectclass=groupOfNames", groupNameAttribute: "cn"}, "(objectclass=groupOfNames)", nil},
|
||||||
|
{"Empty name with complex filter", args{oldFilter: "(&(objectClass=groupOfNames)(cn=*sample*))", groupNameAttribute: "cn"}, "(&(objectClass=groupOfNames)(cn=*sample*))", nil},
|
||||||
|
{"Empty name with bad filter", args{oldFilter: "(&(objectClass=groupOfNames),cn=*sample*)", groupNameAttribute: "cn"}, "", ErrInvalidFilter},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := createGroupSearchFilter(tt.args.oldFilter, tt.args.groupName, tt.args.groupNameAttribute); got != tt.want {
|
if got, err := createGroupSearchFilter(tt.args.oldFilter, tt.args.groupName, tt.args.groupNameAttribute); got != tt.want && err != tt.wantErr {
|
||||||
t.Errorf("createGroupSearchFilter() = %v, want %v", got, tt.want)
|
t.Errorf("createGroupSearchFilter() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -258,7 +261,7 @@ func TestSession_SearchGroup(t *testing.T) {
|
|||||||
ldapConn *goldap.Conn
|
ldapConn *goldap.Conn
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
baseDN string
|
groupDN string
|
||||||
filter string
|
filter string
|
||||||
groupName string
|
groupName string
|
||||||
groupNameAttribute string
|
groupNameAttribute string
|
||||||
@ -281,7 +284,7 @@ func TestSession_SearchGroup(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"normal search",
|
{"normal search",
|
||||||
fields{ldapConfig: ldapConfig},
|
fields{ldapConfig: ldapConfig},
|
||||||
args{baseDN: "dc=example,dc=com", filter: "objectClass=groupOfNames", groupName: "harbor_users", groupNameAttribute: "cn"},
|
args{groupDN: "cn=harbor_users,ou=groups,dc=example,dc=com", filter: "objectClass=groupOfNames", groupName: "harbor_users", groupNameAttribute: "cn"},
|
||||||
[]models.LdapGroup{{GroupName: "harbor_users", GroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com"}}, false},
|
[]models.LdapGroup{{GroupName: "harbor_users", GroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com"}}, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -292,7 +295,7 @@ func TestSession_SearchGroup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
session.Open()
|
session.Open()
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
got, err := session.searchGroup(tt.args.baseDN, tt.args.filter, tt.args.groupName, tt.args.groupNameAttribute)
|
got, err := session.searchGroup(tt.args.groupDN, tt.args.filter, tt.args.groupName, tt.args.groupNameAttribute)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Session.SearchGroup() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("Session.SearchGroup() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -313,17 +316,36 @@ func TestSession_SearchGroupByDN(t *testing.T) {
|
|||||||
LdapBaseDn: ldapTestConfig[common.LDAPBaseDN].(string),
|
LdapBaseDn: ldapTestConfig[common.LDAPBaseDN].(string),
|
||||||
}
|
}
|
||||||
ldapGroupConfig := models.LdapGroupConf{
|
ldapGroupConfig := models.LdapGroupConf{
|
||||||
LdapGroupBaseDN: "ou=group,dc=example,dc=com",
|
LdapGroupBaseDN: "dc=example,dc=com",
|
||||||
LdapGroupFilter: "objectclass=groupOfNames",
|
LdapGroupFilter: "objectclass=groupOfNames",
|
||||||
LdapGroupNameAttribute: "cn",
|
LdapGroupNameAttribute: "cn",
|
||||||
LdapGroupSearchScope: 2,
|
LdapGroupSearchScope: 2,
|
||||||
}
|
}
|
||||||
ldapGroupConfig2 := models.LdapGroupConf{
|
ldapGroupConfig2 := models.LdapGroupConf{
|
||||||
LdapGroupBaseDN: "ou=group,dc=example,dc=com",
|
LdapGroupBaseDN: "dc=example,dc=com",
|
||||||
LdapGroupFilter: "objectclass=groupOfNames",
|
LdapGroupFilter: "objectclass=groupOfNames",
|
||||||
LdapGroupNameAttribute: "o",
|
LdapGroupNameAttribute: "o",
|
||||||
LdapGroupSearchScope: 2,
|
LdapGroupSearchScope: 2,
|
||||||
}
|
}
|
||||||
|
groupConfigWithEmptyBaseDN := models.LdapGroupConf{
|
||||||
|
LdapGroupBaseDN: "",
|
||||||
|
LdapGroupFilter: "(objectclass=groupOfNames)",
|
||||||
|
LdapGroupNameAttribute: "cn",
|
||||||
|
LdapGroupSearchScope: 2,
|
||||||
|
}
|
||||||
|
groupConfigWithFilter := models.LdapGroupConf{
|
||||||
|
LdapGroupBaseDN: "dc=example,dc=com",
|
||||||
|
LdapGroupFilter: "(cn=*admin*)",
|
||||||
|
LdapGroupNameAttribute: "cn",
|
||||||
|
LdapGroupSearchScope: 2,
|
||||||
|
}
|
||||||
|
groupConfigWithDifferentGroupDN := models.LdapGroupConf{
|
||||||
|
LdapGroupBaseDN: "dc=harbor,dc=example,dc=com",
|
||||||
|
LdapGroupFilter: "(objectclass=groupOfNames)",
|
||||||
|
LdapGroupNameAttribute: "cn",
|
||||||
|
LdapGroupSearchScope: 2,
|
||||||
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
ldapConfig models.LdapConf
|
ldapConfig models.LdapConf
|
||||||
ldapGroupConfig models.LdapGroupConf
|
ldapGroupConfig models.LdapGroupConf
|
||||||
@ -346,7 +368,7 @@ func TestSession_SearchGroupByDN(t *testing.T) {
|
|||||||
{"search non-exist group",
|
{"search non-exist group",
|
||||||
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig},
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig},
|
||||||
args{groupDN: "cn=harbor_non_users,ou=groups,dc=example,dc=com"},
|
args{groupDN: "cn=harbor_non_users,ou=groups,dc=example,dc=com"},
|
||||||
nil, true},
|
[]models.LdapGroup{}, false},
|
||||||
{"search invalid group dn",
|
{"search invalid group dn",
|
||||||
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig},
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig},
|
||||||
args{groupDN: "random string"},
|
args{groupDN: "random string"},
|
||||||
@ -359,6 +381,26 @@ func TestSession_SearchGroupByDN(t *testing.T) {
|
|||||||
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig2},
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: ldapGroupConfig2},
|
||||||
args{groupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"},
|
args{groupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"},
|
||||||
[]models.LdapGroup{{GroupName: "hgroup", GroupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"}}, false},
|
[]models.LdapGroup{{GroupName: "hgroup", GroupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"}}, false},
|
||||||
|
{"search with empty group base dn",
|
||||||
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: groupConfigWithEmptyBaseDN},
|
||||||
|
args{groupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"},
|
||||||
|
[]models.LdapGroup{{GroupName: "harbor_group", GroupDN: "cn=harbor_group,ou=groups,dc=example,dc=com"}}, false},
|
||||||
|
{"search with group filter success",
|
||||||
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: groupConfigWithFilter},
|
||||||
|
args{groupDN: "cn=harbor_admin,ou=groups,dc=example,dc=com"},
|
||||||
|
[]models.LdapGroup{{GroupName: "harbor_admin", GroupDN: "cn=harbor_admin,ou=groups,dc=example,dc=com"}}, false},
|
||||||
|
{"search with group filter fail",
|
||||||
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: groupConfigWithFilter},
|
||||||
|
args{groupDN: "cn=harbor_users,ou=groups,dc=example,dc=com"},
|
||||||
|
[]models.LdapGroup{}, false},
|
||||||
|
{"search with different group base dn success",
|
||||||
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: groupConfigWithDifferentGroupDN},
|
||||||
|
args{groupDN: "cn=harbor_root,dc=harbor,dc=example,dc=com"},
|
||||||
|
[]models.LdapGroup{{GroupName: "harbor_root", GroupDN: "cn=harbor_root,dc=harbor,dc=example,dc=com"}}, false},
|
||||||
|
{"search with different group base dn fail",
|
||||||
|
fields{ldapConfig: ldapConfig, ldapGroupConfig: groupConfigWithDifferentGroupDN},
|
||||||
|
args{groupDN: "cn=harbor_guest,ou=groups,dc=example,dc=com"},
|
||||||
|
[]models.LdapGroup{}, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -381,6 +423,35 @@ func TestSession_SearchGroupByDN(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateUserSearchFilter(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
origFilter string
|
||||||
|
ldapUID string
|
||||||
|
username string
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in args
|
||||||
|
want string
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{name: `Normal test`, in: args{"(objectclass=inetorgperson)", "cn", "sample"}, want: "(&(objectclass=inetorgperson)(cn=sample)", wantErr: nil},
|
||||||
|
{name: `Bad original filter`, in: args{"(objectclass=inetorgperson)ldap*", "cn", "sample"}, want: "", wantErr: ErrInvalidFilter},
|
||||||
|
{name: `Complex original filter`, in: args{"(&(objectclass=inetorgperson)(|(memberof=cn=harbor_users,ou=groups,dc=example,dc=com)(memberof=cn=harbor_admin,ou=groups,dc=example,dc=com)(memberof=cn=harbor_guest,ou=groups,dc=example,dc=com)))", "cn", "sample"}, want: "(&(&(objectclass=inetorgperson)(|(memberof=cn=harbor_users,ou=groups,dc=example,dc=com)(memberof=cn=harbor_admin,ou=groups,dc=example,dc=com)(memberof=cn=harbor_guest,ou=groups,dc=example,dc=com)))(cn=sample)", wantErr: nil},
|
||||||
|
{name: `Empty original filter`, in: args{"", "cn", "sample"}, want: "(cn=sample)", wantErr: nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, gotErr := createUserSearchFilter(tt.in.origFilter, tt.in.ldapUID, tt.in.origFilter)
|
||||||
|
if got != tt.want && gotErr != tt.wantErr {
|
||||||
|
t.Errorf(`(%v) = %v; want "%v"`, tt.in, got, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeFilter(t *testing.T) {
|
func TestNormalizeFilter(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
filter string
|
filter string
|
||||||
@ -390,9 +461,11 @@ func TestNormalizeFilter(t *testing.T) {
|
|||||||
args args
|
args args
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{"normal test", args{"(objectclass=user)"}, "objectclass=user"},
|
{"normal test", args{"(objectclass=user)"}, "(objectclass=user)"},
|
||||||
{"with space", args{" (objectclass=user) "}, "objectclass=user"},
|
{"with space", args{" (objectclass=user) "}, "(objectclass=user)"},
|
||||||
{"nothing", args{"objectclass=user"}, "objectclass=user"},
|
{"nothing", args{"objectclass=user"}, "(objectclass=user)"},
|
||||||
|
{"and condition", args{"&(objectclass=user)(cn=admin)"}, "(&(objectclass=user)(cn=admin))"},
|
||||||
|
{"or condition", args{"|(objectclass=user)(cn=admin)"}, "(|(objectclass=user)(cn=admin))"},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -773,6 +773,20 @@ uid: admin_user
|
|||||||
uidnumber: 6003
|
uidnumber: 6003
|
||||||
userpassword: {MD5}wb68DeX0CyENafzUADNn9A==
|
userpassword: {MD5}wb68DeX0CyENafzUADNn9A==
|
||||||
memberof: cn=harbor_admin,ou=groups,dc=example,dc=com
|
memberof: cn=harbor_admin,ou=groups,dc=example,dc=com
|
||||||
|
memberof: cn=harbor_root,dc=harbor,dc=example,dc=com
|
||||||
|
|
||||||
|
|
||||||
|
dn: dc=harbor,dc=example,dc=com
|
||||||
|
associateddomain: harbor
|
||||||
|
dc: harbor
|
||||||
|
objectclass: dNSDomain
|
||||||
|
objectclass: domainRelatedObject
|
||||||
|
objectclass: top
|
||||||
|
|
||||||
|
# Group Entry harbor_admin
|
||||||
|
dn: cn=harbor_root,dc=harbor,dc=example,dc=com
|
||||||
|
cn: harbor_root
|
||||||
|
description: harbor root users
|
||||||
|
member: cn=admin_user,ou=people,dc=example,dc=com
|
||||||
|
objectclass: groupOfNames
|
||||||
|
objectclass: top
|
Loading…
Reference in New Issue
Block a user