authelia/internal/authentication/ldap_user_provider_test.go
Clément Michaud cc6650dbcd
[BUGFIX] [BREAKING] Set username retrieved from authentication backend in session. (#687)
* [BUGFIX] Set username retrieved from authentication backend in session.

In some setups, binding is case insensitive but Authelia is case
sensitive and therefore need the actual username as stored in the
authentication backend in order for Authelia to work correctly.

Fixes #561.

* Use uid attribute as unique user identifier in suites.

* Fix the integration tests.

* Update config.template.yml

* Compute user filter based on username attribute and users_filter.

The filter provided in users_filter is now combined with a filter
based on the username attribute to perform the LDAP search query
finding a user object from the username.

* Fix LDAP based integration tests.

* Update `users_filter` reference examples
2020-03-15 18:10:25 +11:00

345 lines
9.6 KiB
Go

package authentication
import (
"testing"
"github.com/authelia/authelia/internal/configuration/schema"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ldap.v3"
)
func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
}, mockFactory)
mockFactory.EXPECT().
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
Return(mockConn, nil)
mockConn.EXPECT().
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
Return(nil)
_, err := ldap.connect("cn=admin,dc=example,dc=com", "password")
require.NoError(t, err)
}
func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldaps://127.0.0.1:389",
}, mockFactory)
mockFactory.EXPECT().
DialTLS(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389"), gomock.Any()).
Return(mockConn, nil)
mockConn.EXPECT().
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
Return(nil)
_, err := ldap.connect("cn=admin,dc=example,dc=com", "password")
require.NoError(t, err)
}
func TestEscapeSpecialChars(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
ldap := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldaps://127.0.0.1:389",
}, mockFactory)
// No escape
assert.Equal(t, "xyz", ldap.ldapEscape("xyz"))
// Escape
assert.Equal(t, "test\\,abc", ldap.ldapEscape("test,abc"))
assert.Equal(t, "test\\\\abc", ldap.ldapEscape("test\\abc"))
assert.Equal(t, "test\\#abc", ldap.ldapEscape("test#abc"))
assert.Equal(t, "test\\+abc", ldap.ldapEscape("test+abc"))
assert.Equal(t, "test\\<abc", ldap.ldapEscape("test<abc"))
assert.Equal(t, "test\\>abc", ldap.ldapEscape("test>abc"))
assert.Equal(t, "test\\;abc", ldap.ldapEscape("test;abc"))
assert.Equal(t, "test\\\"abc", ldap.ldapEscape("test\"abc"))
assert.Equal(t, "test\\=abc", ldap.ldapEscape("test=abc"))
}
type SearchRequestMatcher struct {
expected string
}
func NewSearchRequestMatcher(expected string) *SearchRequestMatcher {
return &SearchRequestMatcher{expected}
}
func (srm *SearchRequestMatcher) Matches(x interface{}) bool {
sr := x.(*ldap.SearchRequest)
return sr.Filter == srm.expected
}
func (srm *SearchRequestMatcher) String() string {
return ""
}
func TestShouldEscapeUserInput(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsernameAttribute: "uid",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
}, mockFactory)
mockConn.EXPECT().
// Here we ensure that the input has been correctly escaped.
Search(NewSearchRequestMatcher("(uid=john\\=abc)")).
Return(&ldap.SearchResult{}, nil)
ldapClient.getUserProfile(mockConn, "john=abc")
}
func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
UsernameAttribute: "uid",
UsersFilter: "(&(objectCategory=person)(objectClass=user))",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
}, mockFactory)
mockConn.EXPECT().
Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")).
Return(&ldap.SearchResult{}, nil)
ldapClient.getUserProfile(mockConn, "john")
}
func createSearchResultWithAttributes(attributes ...*ldap.EntryAttribute) *ldap.SearchResult {
return &ldap.SearchResult{
Entries: []*ldap.Entry{
&ldap.Entry{Attributes: attributes},
},
}
}
func createSearchResultWithAttributeValues(values ...string) *ldap.SearchResult {
return createSearchResultWithAttributes(&ldap.EntryAttribute{
Values: values,
})
}
func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
UsersFilter: "uid={0}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
}, mockFactory)
mockFactory.EXPECT().
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
Return(mockConn, nil)
mockConn.EXPECT().
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
Return(nil)
mockConn.EXPECT().
Close()
searchGroups := mockConn.EXPECT().
Search(gomock.Any()).
Return(createSearchResultWithAttributes(), nil)
searchProfile := mockConn.EXPECT().
Search(gomock.Any()).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
&ldap.Entry{
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{
Name: "mail",
Values: []string{"test@example.com"},
},
&ldap.EntryAttribute{
Name: "uid",
Values: []string{"john"},
},
},
},
},
}, nil)
gomock.InOrder(searchGroups, searchProfile)
details, err := ldapClient.GetDetails("john")
require.NoError(t, err)
assert.ElementsMatch(t, details.Groups, []string{})
assert.ElementsMatch(t, details.Emails, []string{"test@example.com"})
assert.Equal(t, details.Username, "john")
}
func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
UsernameAttribute: "uid",
UsersFilter: "uid={0}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
}, mockFactory)
mockFactory.EXPECT().
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
Return(mockConn, nil)
mockConn.EXPECT().
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
Return(nil)
mockConn.EXPECT().
Close()
searchGroups := mockConn.EXPECT().
Search(gomock.Any()).
Return(createSearchResultWithAttributeValues("group1", "group2"), nil)
searchProfile := mockConn.EXPECT().
Search(gomock.Any()).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
&ldap.Entry{
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{
Name: "uid",
Values: []string{"john"},
},
},
},
},
}, nil)
gomock.InOrder(searchGroups, searchProfile)
details, err := ldapClient.GetDetails("john")
require.NoError(t, err)
assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"})
assert.ElementsMatch(t, details.Emails, []string{})
assert.Equal(t, details.Username, "john")
}
func TestShouldReturnUsernameFromLDAP(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockFactory := NewMockLDAPConnectionFactory(ctrl)
mockConn := NewMockLDAPConnection(ctrl)
ldapClient := NewLDAPUserProviderWithFactory(schema.LDAPAuthenticationBackendConfiguration{
URL: "ldap://127.0.0.1:389",
User: "cn=admin,dc=example,dc=com",
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
UsersFilter: "uid={0}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
}, mockFactory)
mockFactory.EXPECT().
Dial(gomock.Eq("tcp"), gomock.Eq("127.0.0.1:389")).
Return(mockConn, nil)
mockConn.EXPECT().
Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).
Return(nil)
mockConn.EXPECT().
Close()
searchGroups := mockConn.EXPECT().
Search(gomock.Any()).
Return(createSearchResultWithAttributeValues("group1", "group2"), nil)
searchProfile := mockConn.EXPECT().
Search(gomock.Any()).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
&ldap.Entry{
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{
Name: "mail",
Values: []string{"test@example.com"},
},
&ldap.EntryAttribute{
Name: "uid",
Values: []string{"John"},
},
},
},
},
}, nil)
gomock.InOrder(searchGroups, searchProfile)
details, err := ldapClient.GetDetails("john")
require.NoError(t, err)
assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"})
assert.ElementsMatch(t, details.Emails, []string{"test@example.com"})
assert.Equal(t, details.Username, "John")
}